mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
532 lines
15 KiB
Dart
532 lines
15 KiB
Dart
// 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>[
|
|
ColoredBox(color: Colors.grey.shade200, child: Options(_model)),
|
|
Expanded(
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: <Widget>[
|
|
Container(
|
|
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(
|
|
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 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);
|
|
}
|
|
}
|
|
}
|