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

Implemented Level indicator #30

Merged
merged 11 commits into from
Apr 19, 2021
16 changes: 16 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class Demo extends StatefulWidget {
class _DemoState extends State<Demo> {
bool value = false;

double sliderValue = 0;
double ratingValue = 0;

@override
Widget build(BuildContext context) {
return Scaffold(
Expand All @@ -49,6 +52,19 @@ class _DemoState extends State<Demo> {
child: Text('Button'),
onPressed: () {},
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CapacityIndicator(
value: sliderValue,
onChanged: (v) => setState(() => sliderValue = v),
discrete: true,
),
),
RatingIndicator(
value: ratingValue,
onChanged: (v) => setState(() => ratingValue = v),
),
RelevanceIndicator(value: 10),
],
),
);
Expand Down
13 changes: 10 additions & 3 deletions lib/macos_ui.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
library macos_ui;

/// todo: package-level docs
export 'package:flutter/cupertino.dart'
show CupertinoColors, CupertinoDynamicColor;
export 'package:flutter/widgets.dart';
export 'package:flutter/material.dart'
show
Brightness,
Expand All @@ -12,7 +11,9 @@ export 'package:flutter/material.dart'
PageTransitionsBuilder,
FlutterLogo,
CircleAvatar;
export 'package:flutter/widgets.dart' hide Icon, TextBox;

export 'package:flutter/cupertino.dart'
show CupertinoColors, CupertinoDynamicColor, CupertinoIcons;

export 'src/buttons/push_button.dart';
export 'src/buttons/push_button_theme.dart';
Expand All @@ -30,6 +31,12 @@ export 'src/macos_app.dart';
export 'src/styles/macos_theme.dart';
export 'src/styles/macos_theme_data.dart';
export 'src/styles/typography.dart';
export 'src/layout/scaffold.dart';
export 'src/buttons/switch.dart';
export 'src/styles/typography.dart';
export 'src/util.dart';
export 'src/util.dart';
export 'src/indicators/capacity_indicators.dart';
export 'src/indicators/progress_indicators.dart';
export 'src/indicators/rating_indicator.dart';
export 'src/indicators/relevance_indicator.dart';
184 changes: 184 additions & 0 deletions lib/src/indicators/capacity_indicators.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import 'package:macos_ui/macos_ui.dart';

/// A capacity indicator illustrates the current level in
/// relation to a finite capacity. Capacity indicators are
/// often used when communicating factors like disk and
/// battery usage. Mail, for example, uses a capacity indicator
/// to show the percentage of data used in relation to an
/// email account’s quota.
///
/// There are two types of capacity indicators:
///
/// * Continuous
///
/// A horizontal translucent track that fills with a colored bar
/// to indicate the current value. Tick marks are often displayed
/// to provide context.
///
/// ![Continuous Capacity Indicator](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-continous.png)
///
/// * Discrete
///
/// A horizontal row of separate, equally sized, rectangular segments.
/// The number of segments matches the total capacity, and the segments
/// fill completely—never partially—with color to indicate the current
/// value.
///
/// ![Discrete Capacity Indicator](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-discrete.png)
class CapacityIndicator extends StatelessWidget {
/// Creates a capacity indicator.
///
/// [value] must be in range of 0 to 100.
const CapacityIndicator({
Key? key,
required this.value,
this.onChanged,
this.discrete = false,
this.splits = 10,
}) : assert(value >= 0 && value <= 100),
super(key: key);

/// The current value of the indicator. Must be in range of 0 to 100.
final double value;

/// Called when the current value of the indicator changes.
final ValueChanged<double>? onChanged;

/// Whether the indicator is discrete or not
///
/// ![Discrete Capacity Indicator](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-discrete.png)
final bool discrete;

/// How many parts the indicator will be splitted in if [discrete]
/// is true. Defaults to 10.
final int splits;

void _handleUpdate(Offset lp) {
double value = discrete ? lp.dx / splits : lp.dx;
if (value.isNegative)
value = 0;
else if (value > 100) value = 100;
onChanged?.call(value);
}

@override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (event) => _handleUpdate(event.localPosition),
onPanUpdate: (event) => _handleUpdate(event.localPosition),
onPanDown: (event) => _handleUpdate(event.localPosition),
child: Container(
constraints: BoxConstraints(minWidth: 100),
child: discrete
? LayoutBuilder(builder: (context, consts) {
double width = consts.biggest.width;
if (width.isInfinite) width = 100;
final splitWidth = width / splits;
final fillToIndex = (100 - -(value - 100)) * (splits / 10);
return SizedBox(
width: width,
child: Row(
children: List.generate(splits, (index) {
return Container(
padding: EdgeInsets.only(
right: index == splits - 1 ? 0 : 2.0,
),
width: splitWidth,
child: CapacityIndicatorCell(
value:
value > 0 && fillToIndex / 10 >= index ? 100 : 0,
),
);
}),
),
);
})
: CapacityIndicatorCell(value: value),
),
);
}
}

class CapacityIndicatorCell extends StatelessWidget {
const CapacityIndicatorCell({
Key? key,
this.value = 100,
this.color = CupertinoColors.systemGreen,
this.borderColor = CupertinoColors.tertiaryLabel,
this.backgroundColor = CupertinoColors.tertiarySystemGroupedBackground,
}) : assert(value >= 0 && value <= 100),
super(key: key);

final Color color;
final Color backgroundColor;
final Color borderColor;

final double value;

@override
Widget build(BuildContext context) {
return Container(
height: 16,
child: CustomPaint(
painter: _CapacityCellPainter(
color: CupertinoDynamicColor.resolve(color, context),
backgroundColor:
CupertinoDynamicColor.resolve(backgroundColor, context),
borderColor: CupertinoDynamicColor.resolve(borderColor, context),
value: value,
),
),
);
}
}

class _CapacityCellPainter extends CustomPainter {
const _CapacityCellPainter({
required this.color,
required this.backgroundColor,
required this.borderColor,
required this.value,
});

final Color color;
final Color backgroundColor;
final Color borderColor;
final double value;

@override
void paint(Canvas canvas, Size size) {
final radius = 2.0;

/// Draw background
canvas.drawRRect(
BorderRadius.circular(radius).toRRect(Offset.zero & size),
Paint()..color = backgroundColor,
);

/// Draw inside
canvas.drawRRect(
BorderRadius.horizontal(
left: Radius.circular(radius),
right: value == 100 ? Radius.circular(radius) : Radius.zero,
).toRRect(
Offset.zero & Size(size.width * (value / 100).clamp(0, 1), size.height),
),
Paint()..color = color,
);

/// Draw border
canvas.drawRRect(
BorderRadius.circular(radius).toRRect(Offset.zero & size),
Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = 0.5,
);
}

@override
bool shouldRepaint(_CapacityCellPainter oldDelegate) => false;

@override
bool shouldRebuildSemantics(_CapacityCellPainter oldDelegate) => false;
}
87 changes: 87 additions & 0 deletions lib/src/indicators/rating_indicator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:macos_ui/macos_ui.dart';

/// A rating indicator uses a series of horizontally arranged
/// graphical symbols to communicate a ranking level. The
/// default symbol is a star.
///
/// A rating indicator doesn’t display partial symbols—its value
/// is rounded in order to display complete symbols only. Within
/// a rating indicator, symbols are always the same distance apart
/// and don't expand or shrink to fit the control.
class RatingIndicator extends StatelessWidget {
/// Creates a rating indicator.
///
/// [iconSize] must be non-negative.
///
/// [amount] must be greater than 0
///
/// [value] must be in range of 0 to [amount]
const RatingIndicator({
Key? key,
required this.value,
this.amount = 5,
this.ratedIcon = CupertinoIcons.star_fill,
this.unratedIcon = CupertinoIcons.star,
this.iconColor,
this.iconSize = 16,
this.onChanged,
}) : assert(iconSize >= 0),
assert(amount > 0),
assert(value >= 0 && value <= amount),
super(key: key);

/// The icon used when the star is rated. [CupertinoIcons.star_fill]
/// is used by default. If you must replace the star with a custom
/// symbol, ensure that its purpose is clear.
final IconData ratedIcon;

/// The icon used when the star is unrated. [CupertinoIcons.star] is
/// used by default. If you must replace the star with a custom symbol,
/// ensure that its purpose is clear.
final IconData unratedIcon;

/// The color of the icon. If null, [Style.primaryColor] is used
final Color? iconColor;

/// The size of the icon. Defaults to 16px
final double iconSize;

/// The amount of stars in the indicator. Defaults to 5
final int amount;

/// The current value. It must be in range of 0 to [amount]
final double value;

/// Called when the current value of the indicator changes.
final ValueChanged<double>? onChanged;

void _handleUpdate(Offset lp) {
double value = lp.dx / iconSize;
if (value.isNegative)
value = 0;
else if (value > amount) value = amount.toDouble();
onChanged?.call(value);
}

@override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (event) => _handleUpdate(event.localPosition),
onPanUpdate: (event) => _handleUpdate(event.localPosition),
onPanDown: (event) => _handleUpdate(event.localPosition),
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(amount, (index) {
final rated = value > index;
return Icon(
rated ? ratedIcon : unratedIcon,
color: iconColor ??
context.macosTheme.primaryColor ??
CupertinoColors.activeBlue,
size: iconSize,
);
}),
),
);
}
}
Loading