blob: 7a4e893badbaa9db33a99f0fff4ba59de6d89507 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
enum LerpTarget {
circle,
roundedRect,
rect,
stadium,
polygon,
star,
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
final OptionModel _model = OptionModel();
final TextEditingController textController = TextEditingController();
@override
void initState() {
super.initState();
_model.addListener(_modelChanged);
}
@override
void dispose() {
super.dispose();
_model.removeListener(_modelChanged);
}
void _modelChanged() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: const Text('Star Border'),
backgroundColor: const Color(0xff323232),
),
body: Column(
children: <Widget>[
Container(color: Colors.grey.shade200, child: Options(_model)),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
key: UniqueKey(),
alignment: Alignment.center,
width: 300,
height: 200,
decoration: ShapeDecoration(
color: Colors.blue.shade100,
shape: lerpBorder(
StarBorder.polygon(
side: const BorderSide(strokeAlign: BorderSide.strokeAlignCenter, width: 2),
sides: _model.points,
pointRounding: _model.pointRounding,
rotation: _model.rotation,
squash: _model.squash,
),
_model._lerpTarget,
_model._lerpAmount,
to: _model.lerpTo,
)!,
),
child: const Text('Polygon'),
),
Container(
key: UniqueKey(),
alignment: Alignment.center,
width: 300,
height: 200,
decoration: ShapeDecoration(
color: Colors.blue.shade100,
shape: lerpBorder(
StarBorder(
side: const BorderSide(strokeAlign: BorderSide.strokeAlignCenter, width: 2),
points: _model.points,
innerRadiusRatio: _model.innerRadiusRatio,
pointRounding: _model.pointRounding,
valleyRounding: _model.valleyRounding,
rotation: _model.rotation,
squash: _model.squash,
),
_model._lerpTarget,
_model._lerpAmount,
to: _model.lerpTo,
)!,
),
child: const Text('Star'),
),
],
),
),
],
),
),
);
}
}
class OptionModel extends ChangeNotifier {
double get pointRounding => _pointRounding;
double _pointRounding = 0.0;
set pointRounding(double value) {
if (value != _pointRounding) {
_pointRounding = value;
if (_valleyRounding + _pointRounding > 1) {
_valleyRounding = 1.0 - _pointRounding;
}
notifyListeners();
}
}
double get valleyRounding => _valleyRounding;
double _valleyRounding = 0.0;
set valleyRounding(double value) {
if (value != _valleyRounding) {
_valleyRounding = value;
if (_valleyRounding + _pointRounding > 1) {
_pointRounding = 1.0 - _valleyRounding;
}
notifyListeners();
}
}
double get squash => _squash;
double _squash = 0.0;
set squash(double value) {
if (value != _squash) {
_squash = value;
notifyListeners();
}
}
double get rotation => _rotation;
double _rotation = 0.0;
set rotation(double value) {
if (value != _rotation) {
_rotation = value;
notifyListeners();
}
}
double get innerRadiusRatio => _innerRadiusRatio;
double _innerRadiusRatio = 0.4;
set innerRadiusRatio(double value) {
if (value != _innerRadiusRatio) {
_innerRadiusRatio = clampDouble(value, 0.0001, double.infinity);
notifyListeners();
}
}
double get points => _points;
double _points = 5;
set points(double value) {
if (value != _points) {
_points = value;
notifyListeners();
}
}
double get lerpAmount => _lerpAmount;
double _lerpAmount = 0.0;
set lerpAmount(double value) {
if (value != _lerpAmount) {
_lerpAmount = value;
notifyListeners();
}
}
bool get lerpTo => _lerpTo;
bool _lerpTo = true;
set lerpTo(bool value) {
if (_lerpTo != value) {
_lerpTo = value;
notifyListeners();
}
}
LerpTarget get lerpTarget => _lerpTarget;
LerpTarget _lerpTarget = LerpTarget.circle;
set lerpTarget(LerpTarget value) {
if (value != _lerpTarget) {
_lerpTarget = value;
notifyListeners();
}
}
void reset() {
final OptionModel defaultModel = OptionModel();
_pointRounding = defaultModel.pointRounding;
_valleyRounding = defaultModel.valleyRounding;
_rotation = defaultModel.rotation;
_squash = defaultModel.squash;
_lerpAmount = defaultModel.lerpAmount;
_lerpTo = defaultModel.lerpTo;
_lerpTarget = defaultModel.lerpTarget;
_innerRadiusRatio = defaultModel._innerRadiusRatio;
_points = defaultModel.points;
notifyListeners();
}
}
class LabeledCheckbox extends StatelessWidget {
const LabeledCheckbox({super.key, required this.label, this.onChanged, this.value});
final String label;
final ValueChanged<bool?>? onChanged;
final bool? value;
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Checkbox(
onChanged: onChanged,
value: value,
),
Text(label),
],
);
}
}
class Options extends StatefulWidget {
const Options(this.model, {super.key});
final OptionModel model;
@override
State<Options> createState() => _OptionsState();
}
class _OptionsState extends State<Options> {
@override
void initState() {
super.initState();
widget.model.addListener(_modelChanged);
}
@override
void didUpdateWidget(Options oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.model != oldWidget.model) {
oldWidget.model.removeListener(_modelChanged);
widget.model.addListener(_modelChanged);
}
}
@override
void dispose() {
super.dispose();
widget.model.removeListener(_modelChanged);
}
void _modelChanged() {
setState(() {});
}
double sliderValue = 0.0;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(5.0, 0.0, 5.0, 10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: ControlSlider(
label: 'Point Rounding',
value: widget.model.pointRounding,
onChanged: (double value) {
widget.model.pointRounding = value;
},
),
),
Expanded(
child: ControlSlider(
label: 'Valley Rounding',
value: widget.model.valleyRounding,
onChanged: (double value) {
widget.model.valleyRounding = value;
},
),
),
],
),
Row(
children: <Widget>[
Expanded(
child: ControlSlider(
label: 'Squash',
value: widget.model.squash,
onChanged: (double value) {
widget.model.squash = value;
},
),
),
Expanded(
child: ControlSlider(
label: 'Rotation',
value: widget.model.rotation,
max: 360,
onChanged: (double value) {
widget.model.rotation = value;
},
),
),
],
),
Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Expanded(
child: ControlSlider(
label: 'Points',
value: widget.model.points,
min: 2,
max: 20,
onChanged: (double value) {
widget.model.points = value;
},
),
),
OutlinedButton(
child: const Text('Nearest'),
onPressed: () {
widget.model.points = widget.model.points.roundToDouble();
}),
],
),
),
Expanded(
child: ControlSlider(
label: 'Inner Radius',
value: widget.model.innerRadiusRatio,
onChanged: (double value) {
widget.model.innerRadiusRatio = value;
},
),
),
],
),
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsetsDirectional.only(end: 8.0),
child: ControlSlider(
label: 'Lerp',
value: widget.model.lerpAmount,
onChanged: (double value) {
widget.model.lerpAmount = value;
},
),
),
),
Padding(
padding: const EdgeInsetsDirectional.only(start: 8.0, end: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(children: <Widget>[
Radio<bool>(
value: true,
groupValue: widget.model.lerpTo,
onChanged: (bool? value) {
widget.model.lerpTo = value!;
}),
const Text('To'),
]),
Row(children: <Widget>[
Radio<bool>(
value: false,
groupValue: widget.model.lerpTo,
onChanged: (bool? value) {
widget.model.lerpTo = value!;
}),
const Text('From'),
])
],
),
),
Expanded(
child: Row(
children: <Widget>[
Expanded(
child: DropdownButton<LerpTarget>(
items: LerpTarget.values.map<DropdownMenuItem<LerpTarget>>((LerpTarget target) {
return DropdownMenuItem<LerpTarget>(value: target, child: Text(target.name));
}).toList(),
value: widget.model.lerpTarget,
onChanged: (LerpTarget? value) {
if (value == null) {
return;
}
widget.model.lerpTarget = value;
},
),
),
],
),
),
],
),
ElevatedButton(
onPressed: () {
widget.model.reset();
sliderValue = 0.0;
},
child: const Text('Reset'),
),
],
),
);
}
}
class ControlSlider extends StatelessWidget {
const ControlSlider({
super.key,
required this.label,
required this.value,
required this.onChanged,
this.min = 0.0,
this.max = 1.0,
});
final String label;
final double value;
final void Function(double value) onChanged;
final double min;
final double max;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(label),
Expanded(
child: Slider(
label: value.toStringAsFixed(1),
onChanged: onChanged,
min: min,
max: max,
value: value,
),
),
Text(
value.toStringAsFixed(3),
),
],
),
);
}
}
const Color lerpToColor = Colors.red;
const BorderSide lerpToBorder = BorderSide(width: 5, color: lerpToColor);
ShapeBorder? lerpBorder(StarBorder border, LerpTarget target, double t, {bool to = true}) {
switch (target) {
case LerpTarget.circle:
if (to) {
return border.lerpTo(const CircleBorder(side: lerpToBorder, eccentricity: 0.5), t);
} else {
return border.lerpFrom(const CircleBorder(side: lerpToBorder, eccentricity: 0.5), t);
}
case LerpTarget.roundedRect:
if (to) {
return border.lerpTo(
const RoundedRectangleBorder(
side: lerpToBorder,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
t,
);
} else {
return border.lerpFrom(
const RoundedRectangleBorder(
side: lerpToBorder,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
t,
);
}
case LerpTarget.rect:
if (to) {
return border.lerpTo(const RoundedRectangleBorder(side: lerpToBorder), t);
} else {
return border.lerpFrom(const RoundedRectangleBorder(side: lerpToBorder), t);
}
case LerpTarget.stadium:
if (to) {
return border.lerpTo(const StadiumBorder(side: lerpToBorder), t);
} else {
return border.lerpFrom(const StadiumBorder(side: lerpToBorder), t);
}
case LerpTarget.polygon:
if (to) {
return border.lerpTo(const StarBorder.polygon(side: lerpToBorder, sides: 4), t);
} else {
return border.lerpFrom(const StarBorder.polygon(side: lerpToBorder, sides: 4), t);
}
case LerpTarget.star:
if (to) {
return border.lerpTo(const StarBorder(side: lerpToBorder, innerRadiusRatio: .5), t);
} else {
return border.lerpFrom(const StarBorder(side: lerpToBorder, innerRadiusRatio: .5), t);
}
}
}