Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
## Unreleased

### Nodes

- `gauge` node (`GaugeNode`, `GaugeProperties`, `GaugeZone`): a dial with a
bindable `value`, zone bands, LED segments, ticks and labels, a target
notch, a needle and value animation.
- `chart` node (`ChartNode`, `ChartProperties`, `ChartSeries`,
`ChartReferenceLine`): bar, line and timeline charts bound to a list
through `variables.data`; bar, line, area and tick series with stacks,
per-item colors, dashes, end dots and fill-between; reference lines;
bindable `selectedIndex` and a `changed` trigger; a timeline's `nowColor`
colors its now marker.
- `PaintModel.gradientTileMode` (clamp, repeated, mirror) for repeating
gradients such as stripes.
- `BlendMixin.pulse` (`PulseModel`): a looping fade of a node's opacity.
- Rows can wrap: `RowColumnNode.wrap`, `spacing` and `runSpacing` lay a
row's children out in lines like Flutter's `Wrap`.
- `ShowDialogAction.anchor` (`DialogAnchor.trigger`), `placement`,
`anchorGap` and `anchorOffset`: a dialog can open as a popover next to
the node that ran it. `copyWith` keeps `nonBlocking` and `enabled`.
- `ShowToastAction.accentColor` (a 4 px leading edge), `cornerRadius` and
`maxWidth`.
- Slider thumbs can be rectangles: `SliderProperties.thumbShape`,
`thumbWidth`, `thumbHeight`, `thumbCornerRadius`, `thumbBorderColor`,
`thumbBorderWidth` and `thumbGripColor`.
- `MaterialIcon.fromJson` reads a Flutter-style name with a style suffix
(`home_outlined`) as the base name in that style, so it draws.

### Variables

- `VariableData.isComputed` and `isFormulaValue`: a variable whose value is
a `${= ...}` formula is computed; `getValue()` returns null for it.

### JSON

- Gauge and chart nodes read JSON without `properties`; grid delegates,
scroll settings and placeholder type lists default when left out; a
spacer reads JSON without its positioning mode, aspect lock and
reactions.

## 6.1.0

### States and conditions
Expand Down
21 changes: 21 additions & 0 deletions lib/src/api/mixins.dart
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ mixin BlendMixin on BaseNode {
/// Whether to use ink well on this node, along with its properties.
late InkWellModel? inkWell;

/// A looping fade of the node's opacity, or null for none.
PulseModel? pulse;

/// Sets the blend properties of the node.
void setBlendMixin({
required double opacity,
Expand Down Expand Up @@ -562,6 +565,22 @@ enum SliderTrackShapeEnum {
}
}

/// Shape of a slider's thumb.
enum SliderThumbShapeEnum {
/// A circle of the slider's thumb radius.
circle,

/// A rectangle of the slider's thumb width and height, with rounded
/// corners, an optional border and an optional grip line.
rectangle;

/// Display-able string representation of the enum.
String get prettify => switch (this) {
SliderThumbShapeEnum.circle => 'Circle',
SliderThumbShapeEnum.rectangle => 'Rectangle',
};
}

/// When a slider shows its value indicator. Mirrors Flutter's
/// `ShowValueIndicator`.
enum ShowValueIndicatorC {
Expand Down Expand Up @@ -1255,6 +1274,7 @@ mixin ScrollableMixin on BaseNode {
bool get isScrollingEnforced => false;

/// Whether the scrolling is enabled.
@JsonKey(defaultValue: false)
late bool isScrollable;

/// Direction in which the scrolling is allowed.
Expand All @@ -1277,6 +1297,7 @@ mixin ScrollableMixin on BaseNode {

/// Whether to use a [ListView] instead of [SingleChildScrollView] in flutter
/// code-gen and transformers.
@JsonKey(defaultValue: false)
late bool useFlutterListView;

/// Whether to use AlwaysScrollableScrollPhysics or not.
Expand Down
129 changes: 128 additions & 1 deletion lib/src/api/models/action/show_dialog_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,94 @@ import '../models.dart';

part 'show_dialog_action.g.dart';

/// An action that navigates to a new page.
/// Where a [ShowDialogAction] shows its canvas.
enum DialogAnchor {
/// Centered on the screen.
none,

/// Next to the node that ran the action, like a popover or menu, at the
/// action's [ShowDialogAction.placement].
trigger;

/// Displayable name.
String get prettify => switch (this) {
DialogAnchor.none => 'Center of screen',
DialogAnchor.trigger => 'Anchor to trigger',
};
}

/// The side of the trigger an anchored [ShowDialogAction] opens on, and how
/// it lines up with the trigger along that side.
///
/// Start and end follow the reading direction for [below] and [above]
/// placements (start is the left edge in a left-to-right app) and are the top
/// and bottom edges for [right] and [left] placements. When the dialog does
/// not fit on its side, it opens on the opposite side if there is more room
/// there.
enum DialogPlacement {
/// Below the trigger, start edges lined up.
belowStart,

/// Below the trigger, centered on it.
belowCenter,

/// Below the trigger, end edges lined up.
belowEnd,

/// Above the trigger, start edges lined up.
aboveStart,

/// Above the trigger, centered on it.
aboveCenter,

/// Above the trigger, end edges lined up.
aboveEnd,

/// Right of the trigger, top edges lined up.
rightStart,

/// Right of the trigger, centered on it.
rightCenter,

/// Right of the trigger, bottom edges lined up.
rightEnd,

/// Left of the trigger, top edges lined up.
leftStart,

/// Left of the trigger, centered on it.
leftCenter,

/// Left of the trigger, bottom edges lined up.
leftEnd;

/// Whether the dialog opens below or above the trigger, rather than beside
/// it.
bool get isVertical => index <= DialogPlacement.aboveEnd.index;

/// Displayable name.
String get prettify => switch (this) {
DialogPlacement.belowStart => 'Below, start',
DialogPlacement.belowCenter => 'Below, center',
DialogPlacement.belowEnd => 'Below, end',
DialogPlacement.aboveStart => 'Above, start',
DialogPlacement.aboveCenter => 'Above, center',
DialogPlacement.aboveEnd => 'Above, end',
DialogPlacement.rightStart => 'Right, top',
DialogPlacement.rightCenter => 'Right, center',
DialogPlacement.rightEnd => 'Right, bottom',
DialogPlacement.leftStart => 'Left, top',
DialogPlacement.leftCenter => 'Left, center',
DialogPlacement.leftEnd => 'Left, bottom',
};
}

/// An action that shows a canvas as a dialog: centered on the screen, or,
/// with [anchor] set to [DialogAnchor.trigger], as a popover next to the node
/// that ran it (an account menu under an avatar, for example).
///
/// The dialog closes when its barrier is tapped (if [barrierDismissible]) or
/// with a "Navigate back" action inside it.
@JsonSerializable()
class ShowDialogAction extends ActionModel with Equatable, SerializableMixin {
/// Destination canvas node's ID.
Expand All @@ -24,13 +111,36 @@ class ShowDialogAction extends ActionModel with Equatable, SerializableMixin {
/// Whether to show a close button on the dialog.
final bool showCloseButton;

/// Where the dialog shows: centered, or next to the node that ran the
/// action.
@JsonKey(unknownEnumValue: DialogAnchor.none)
final DialogAnchor anchor;

/// The side of the trigger an anchored dialog opens on. Only used when
/// [anchor] is [DialogAnchor.trigger].
@JsonKey(unknownEnumValue: DialogPlacement.belowStart)
final DialogPlacement placement;

/// The space between the trigger and an anchored dialog, in logical
/// pixels.
final double anchorGap;

/// How far an anchored dialog is shifted along the trigger's edge, in
/// logical pixels: toward the end for [DialogPlacement.isVertical]
/// placements (right in a left-to-right app), down for the others.
final double anchorOffset;

/// Creates a new [ShowDialogAction].
ShowDialogAction({
required this.destinationId,
Map<String, String>? params,
this.barrierColor = ColorRGBA.black54,
this.barrierDismissible = true,
this.showCloseButton = false,
this.anchor = DialogAnchor.none,
this.placement = DialogPlacement.belowStart,
this.anchorGap = 4,
this.anchorOffset = 0,
super.nonBlocking,
super.enabled,
}) : params = {...params ?? {}},
Expand All @@ -43,21 +153,38 @@ class ShowDialogAction extends ActionModel with Equatable, SerializableMixin {
barrierDismissible,
barrierColor,
showCloseButton,
anchor,
placement,
anchorGap,
anchorOffset,
];

/// Whether the dialog opens next to the node that ran the action.
bool get isAnchored => anchor == DialogAnchor.trigger;

/// Duplicates this [ShowDialogAction] with given data overrides.
ShowDialogAction copyWith({
String? destinationId,
Map<String, String>? params,
bool? barrierDismissible,
ColorRGBA? barrierColor,
bool? showCloseButton,
DialogAnchor? anchor,
DialogPlacement? placement,
double? anchorGap,
double? anchorOffset,
}) => ShowDialogAction(
destinationId: destinationId ?? this.destinationId,
params: {...this.params, ...params ?? {}},
barrierDismissible: barrierDismissible ?? this.barrierDismissible,
barrierColor: barrierColor ?? this.barrierColor,
showCloseButton: showCloseButton ?? this.showCloseButton,
anchor: anchor ?? this.anchor,
placement: placement ?? this.placement,
anchorGap: anchorGap ?? this.anchorGap,
anchorOffset: anchorOffset ?? this.anchorOffset,
nonBlocking: nonBlocking,
enabled: enabled,
);

/// Creates a new [ShowDialogAction] instance from a JSON data.
Expand Down
45 changes: 45 additions & 0 deletions lib/src/api/models/action/show_dialog_action.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions lib/src/api/models/action/show_toast_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
/// [ToastPosition.bottom].
final double bottomOffset;

/// Color of a 4 px edge on the toast's leading side, or null for none.
final ColorRGBA? accentColor;

/// Corner radius, or null for the default: a pill, or the theme's button
/// radius when the toast has a button.
final double? cornerRadius;

/// The widest the toast gets.
final double maxWidth;

/// Creates a new [ShowToastAction].
ShowToastAction({
required this.message,
Expand All @@ -103,6 +113,9 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
List<ActionModel>? actions,
this.icon,
this.bottomOffset = 104,
this.accentColor,
this.cornerRadius,
this.maxWidth = 560,
super.nonBlocking,
super.enabled,
}) : actions = actions ?? [],
Expand All @@ -123,6 +136,9 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
actions,
icon,
bottomOffset,
accentColor,
cornerRadius,
maxWidth,
];

/// Duplicates this [ShowToastAction] with given data overrides.
Expand All @@ -140,6 +156,10 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
MultiSourceIconModel? icon,
bool clearIcon = false,
double? bottomOffset,
ColorRGBA? accentColor,
bool clearAccentColor = false,
double? cornerRadius,
double? maxWidth,
}) => ShowToastAction(
message: message ?? this.message,
duration: duration ?? this.duration,
Expand All @@ -153,6 +173,9 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
actions: actions ?? this.actions,
icon: clearIcon ? null : icon ?? this.icon,
bottomOffset: bottomOffset ?? this.bottomOffset,
accentColor: clearAccentColor ? null : accentColor ?? this.accentColor,
cornerRadius: cornerRadius ?? this.cornerRadius,
maxWidth: maxWidth ?? this.maxWidth,
nonBlocking: nonBlocking,
enabled: enabled,
);
Expand Down
Loading
Loading