Skip to content

Commit

Permalink
Implemented Level indicator (#30)
Browse files Browse the repository at this point in the history
* Implement capacity indicator

* Implemented rating indicator

* Implemented relevance indicator

* Implemented gesture detector for indicators

* Updated theme

* Fixed colors

I also made some changes to styles/colors.dart

* Update colors.dart
  • Loading branch information
bdlukaa authored Apr 19, 2021
1 parent 4cffae2 commit 19b9750
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 4 deletions.
2 changes: 2 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

/windows/
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
14 changes: 11 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,13 @@ 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/styles/colors.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';
183 changes: 183 additions & 0 deletions lib/src/indicators/capacity_indicators.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
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: DynamicColorX.macosResolve(color, context),
backgroundColor: DynamicColorX.macosResolve(backgroundColor, context),
borderColor: DynamicColorX.macosResolve(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;
}
90 changes: 90 additions & 0 deletions lib/src/indicators/rating_indicator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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: DynamicColorX.macosResolve(
iconColor ??
context.macosTheme.primaryColor ??
CupertinoColors.activeBlue,
context,
),
size: iconSize,
);
}),
),
);
}
}
Loading

0 comments on commit 19b9750

Please sign in to comment.