Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Progress Indicators #22

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [0.0.3]

* Implemented `ProgressCircle` and `ProgressBar`
* Implemented the `Switch` widget

## [0.0.2]
Expand Down
3 changes: 2 additions & 1 deletion lib/macos_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export 'src/util.dart';
export 'src/styles/theme.dart';
export 'src/styles/typography.dart';
export 'src/layout/scaffold.dart';
export 'src/buttons/switch.dart';
export 'src/buttons/switch.dart';
export 'src/indicators/progress_indicators.dart';
232 changes: 232 additions & 0 deletions lib/src/indicators/progress_indicators.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import 'dart:math' as math;

import 'package:macos_ui/macos_ui.dart';

import 'package:flutter/cupertino.dart' as c;

/// A [ProgressCircle] show progress in a circular form,
/// either as a spinner or as a circle that fills in as
/// progress continues.
class ProgressCircle extends StatelessWidget {
/// Creates a new progress circle.
///
/// [radius] must be non-negative
///
/// [value] must be in the range of 0 and 100
const ProgressCircle({
Key? key,
this.value,
this.radius = 10,
this.innerColor,
this.borderColor,
}) : assert(value == null || value >= 0 && value <= 100),
assert(radius >= 0),
super(key: key);

/// The value of the progress circle. If non-null, this has to
/// be non-negative and less the 100. If null, the progress circle
/// will be considered indeterminate, backed by [c.CupertinoActivityIndicator]
final double? value;

/// The radius of the progress circle. Defaults to 10px
final double radius;

/// The color of the circle at the middle. If null,
/// [CupertinoColors.secondarySystemFill] is used
final Color? innerColor;

/// The color of the border. If null, [CupertinoColors.secondarySystemFill]
/// is used
final Color? borderColor;

/// Whether the progress circle is determinate or not
bool get isDeterminate => value != null;

@override
Widget build(BuildContext context) {
if (isDeterminate) {
return Semantics(
value: value!.toStringAsFixed(2),
child: SizedBox(
height: radius * 2,
width: radius * 2,
child: CustomPaint(
painter: _DeterminateCirclePainter(
value!,
innerColor: CupertinoDynamicColor.resolve(
innerColor ?? CupertinoColors.secondarySystemFill,
context,
),
borderColor: CupertinoDynamicColor.resolve(
borderColor ?? CupertinoColors.secondarySystemFill,
context,
),
),
),
),
);
} else {
return c.CupertinoActivityIndicator(
radius: radius,
);
}
}
}

class _DeterminateCirclePainter extends CustomPainter {
final double value;

final Color? innerColor;
final Color? borderColor;

const _DeterminateCirclePainter(
this.value, {
this.innerColor,
this.borderColor,
});

static const double _twoPi = math.pi * 2.0;
static const double _epsilon = .001;
static const double _sweep = _twoPi - _epsilon;
static const double _startAngle = -math.pi / 2.0;

@override
void paint(Canvas canvas, Size size) {
/// Draw an arc
void drawArc(
double value, {
Paint? paint,
bool useCenter = true,
}) {
canvas.drawArc(
Offset.zero & size,
_startAngle,
(value / 100).clamp(0, 1) * _sweep,
useCenter,
paint ?? Paint()
..color = innerColor ?? CupertinoColors.activeBlue,
);
}

// Draw the inner circle
drawArc(value);

/// Draw the border
drawArc(
100,
useCenter: false,
paint: Paint()
..color = borderColor ?? CupertinoColors.activeBlue
..style = PaintingStyle.stroke,
);
}

@override
bool shouldRepaint(_DeterminateCirclePainter old) => value != old.value;

@override
bool shouldRebuildSemantics(_DeterminateCirclePainter oldDelegate) => false;
}

/// A [ProgressBar] show progress in a horizontal bar.
class ProgressBar extends StatelessWidget {
/// Creates a new progress bar
///
/// [height] more be non-negative
///
/// [value] must be in the range of 0 and 100
const ProgressBar({
Key? key,
this.height = 4.5,
required this.value,
this.trackColor,
this.backgroundColor,
}) : assert(value >= 0 && value <= 100),
assert(height >= 0),
super(key: key);

/// The value of the progress bar. If non-null, this has to
/// be non-negative and less the 100. If null, the progress bar
/// will be considered indeterminate.
final double value;

/// The height of the line. Default to 4.5px
final double height;

/// The color of the track. If null, [Style.accentColor] is used
final Color? trackColor;

/// The color of the background. If null, [CupertinoColors.secondarySystemFill]
/// is used
final Color? backgroundColor;

@override
Widget build(BuildContext context) {
return Semantics(
value: value.toStringAsFixed(2),
child: Container(
constraints: BoxConstraints(
minHeight: height,
maxHeight: height,
minWidth: 85,
),
child: CustomPaint(
painter: _DeterminateBarPainter(
value,
activeColor: trackColor ??
context.maybeStyle?.accentColor ??
CupertinoColors.activeBlue,
backgroundColor: CupertinoDynamicColor.resolve(
backgroundColor ?? CupertinoColors.secondarySystemFill,
context,
),
),
),
),
);
}
}

class _DeterminateBarPainter extends CustomPainter {
final double value;

final Color? backgroundColor;
final Color? activeColor;

const _DeterminateBarPainter(
this.value, {
this.backgroundColor,
this.activeColor,
});

@override
void paint(Canvas canvas, Size size) {
// Draw the background line
canvas.drawRRect(
BorderRadius.circular(100).toRRect(
Offset.zero & size,
),
Paint()
..color = backgroundColor ?? CupertinoColors.secondarySystemFill
..style = PaintingStyle.fill,
);

// Draw the active tick line
canvas.drawRRect(
BorderRadius.horizontal(left: Radius.circular(100)).toRRect(Offset.zero &
Size(
(value / 100).clamp(0.0, 1.0) * size.width,
size.height,
)),
Paint()
..color = activeColor ?? CupertinoColors.activeBlue
..style = PaintingStyle.fill,
);
}

@override
bool shouldRepaint(_DeterminateBarPainter old) => old.value != value;

@override
bool shouldRebuildSemantics(_DeterminateBarPainter oldDelegate) => false;
}