books
This commit is contained in:
4
packages/components/lib/components.dart
Normal file
4
packages/components/lib/components.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
export 'toly_ui/toly_ui.dart';
|
||||
export 'windows/window_buttons.dart';
|
||||
export 'windows/drag_to_move_area.dart';
|
||||
export 'project/demo_shower.dart';
|
||||
150
packages/components/lib/project/demo_shower.dart
Normal file
150
packages/components/lib/project/demo_shower.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
// import 'package:go_router/go_router.dart';
|
||||
|
||||
class DemoShower extends StatefulWidget {
|
||||
final List<Widget> demos;
|
||||
final String srcCodeDir;
|
||||
const DemoShower({super.key, required this.demos, required this.srcCodeDir});
|
||||
|
||||
@override
|
||||
State<DemoShower> createState() => _DemoShowerState();
|
||||
}
|
||||
|
||||
class _DemoShowerState extends State<DemoShower> {
|
||||
final PageController _ctrl = PageController();
|
||||
int _index = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
PageView(
|
||||
controller: _ctrl,
|
||||
children: widget.demos,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
TolyIconButton(
|
||||
onTap: () {
|
||||
_index = (_index - 1) % widget.demos.length;
|
||||
setState(() {});
|
||||
_ctrl.animateToPage(_index,
|
||||
curve: Curves.easeIn,
|
||||
duration: Duration(milliseconds: 200));
|
||||
},
|
||||
iconData: Icons.navigate_before,
|
||||
size: 36,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'第 ${_index + 1}/${widget.demos.length} 页',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
String index = (_index + 1).toString().padLeft(2, '0');
|
||||
String path = 'assets/${widget.srcCodeDir}/s$index.dart';
|
||||
context.push('/code?path=$path');
|
||||
},
|
||||
child: const MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Text(
|
||||
'查看源码',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
TolyIconButton(
|
||||
onTap: () {
|
||||
_index = (_index + 1) % widget.demos.length;
|
||||
setState(() {});
|
||||
_ctrl.animateToPage(_index,
|
||||
curve: Curves.easeIn,
|
||||
duration: Duration(milliseconds: 200));
|
||||
},
|
||||
size: 36,
|
||||
iconData: Icons.navigate_next,
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TolyIconButton extends StatefulWidget {
|
||||
final IconData iconData;
|
||||
final VoidCallback onTap;
|
||||
final double size;
|
||||
const TolyIconButton({
|
||||
super.key,
|
||||
required this.iconData,
|
||||
required this.size,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TolyIconButton> createState() => _TolyIconButtonState();
|
||||
}
|
||||
|
||||
class _TolyIconButtonState extends State<TolyIconButton> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: MouseRegion(
|
||||
onEnter: _onEnter,
|
||||
onExit: _onExit,
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
decoration: BoxDecoration(
|
||||
color: _hover
|
||||
? Colors.grey.withOpacity(0.6)
|
||||
: Colors.grey.withOpacity(0.5),
|
||||
shape: BoxShape.circle),
|
||||
child: Icon(
|
||||
widget.iconData,
|
||||
size: 26,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_hover = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HoverIconButton extends StatefulWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final IconData icon;
|
||||
final double size;
|
||||
final Color? hoverColor;
|
||||
final Color? defaultColor;
|
||||
final MouseCursor cursor;
|
||||
|
||||
const HoverIconButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
this.hoverColor,
|
||||
this.size = 24,
|
||||
this.defaultColor,
|
||||
this.cursor = SystemMouseCursors.click,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HoverIconButton> createState() => _HoverIconButtonState();
|
||||
}
|
||||
|
||||
class _HoverIconButtonState extends State<HoverIconButton> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color? color = (_hover)
|
||||
? widget.hoverColor ?? Theme.of(context).primaryColor
|
||||
: (widget.defaultColor ?? null);
|
||||
|
||||
return MouseRegion(
|
||||
cursor: widget.cursor,
|
||||
onEnter: _onEnter,
|
||||
onExit: _onExit,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onPressed,
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: widget.size,
|
||||
color: color,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_hover = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
5
packages/components/lib/toly_ui/code_view/code/code.dart
Normal file
5
packages/components/lib/toly_ui/code_view/code/code.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
export 'code_widget.dart';
|
||||
export 'high_light_code.dart';
|
||||
export 'highlighter_style.dart';
|
||||
export 'language/language.dart';
|
||||
export 'language/dart_languge.dart';
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
/// create by 张风捷特烈 on 2020-04-15
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'high_light_code.dart';
|
||||
import 'highlighter_style.dart';
|
||||
import 'language/dart_languge.dart';
|
||||
|
||||
class CodeWidget extends StatelessWidget {
|
||||
CodeWidget({Key? key, required this.code,required this.style, this.fontSize = 13,this.fontFamily})
|
||||
: super(key: key);
|
||||
|
||||
final String code;
|
||||
final HighlighterStyle style;
|
||||
final double fontSize;
|
||||
final String? fontFamily;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget body;
|
||||
Widget _codeWidget;
|
||||
try {
|
||||
_codeWidget = RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(fontSize: fontSize,fontFamily: fontFamily),
|
||||
children: <TextSpan>[
|
||||
CodeHighlighter(
|
||||
style: style,
|
||||
language: const DartLanguage()
|
||||
).format(code)],
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
print(err);
|
||||
_codeWidget = Text(code);
|
||||
}
|
||||
body = SingleChildScrollView(
|
||||
child: Container(
|
||||
child: _codeWidget,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: style.backgroundColor ?? const Color(0xffF6F8FA),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5.0))),
|
||||
),
|
||||
);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/// create by 张风捷特烈 on 2020-04-15
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
// Copyright 2016 The Chromium 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/material.dart';
|
||||
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
import 'highlighter_style.dart';
|
||||
import 'language/dart_languge.dart';
|
||||
import 'language/language.dart';
|
||||
|
||||
/// final SyntaxHighlighterStyle style = SyntaxHighlighterStyle.lightThemeStyle();
|
||||
/// DartSyntaxHighlighter(style).format(source)
|
||||
|
||||
abstract class Highlighter {
|
||||
// ignore: one_member_abstracts
|
||||
Language language;
|
||||
|
||||
|
||||
Highlighter({required this.language});
|
||||
|
||||
TextSpan format(String src);
|
||||
}
|
||||
|
||||
//暗黑模式下的高亮样式
|
||||
class CodeHighlighter extends Highlighter {
|
||||
CodeHighlighter(
|
||||
{Language language = const DartLanguage(), HighlighterStyle? style}):super(language: language) {
|
||||
_spans = <_HighlightSpan>[];
|
||||
_style = style ?? HighlighterStyle.fromColors(HighlighterStyle.lightColor);
|
||||
}
|
||||
|
||||
late HighlighterStyle _style;
|
||||
|
||||
String _src='';
|
||||
late StringScanner _scanner;
|
||||
|
||||
List<_HighlightSpan> _spans=[];
|
||||
|
||||
@override
|
||||
TextSpan format(String src) {
|
||||
_src = src;
|
||||
_scanner = StringScanner(_src);
|
||||
|
||||
if (_generateSpans()) {
|
||||
// Successfully parsed the code
|
||||
final List<TextSpan> formattedText = <TextSpan>[];
|
||||
int currentPosition = 0;
|
||||
|
||||
for (_HighlightSpan span in _spans) {
|
||||
if (currentPosition != span.start) {
|
||||
formattedText
|
||||
.add(TextSpan(text: _src.substring(currentPosition, span.start)));
|
||||
}
|
||||
|
||||
formattedText.add(TextSpan(
|
||||
style: span.textStyle(_style), text: span.textForSpan(_src)));
|
||||
|
||||
currentPosition = span.end;
|
||||
}
|
||||
|
||||
if (currentPosition != _src.length) {
|
||||
formattedText
|
||||
.add(TextSpan(text: _src.substring(currentPosition, _src.length)));
|
||||
}
|
||||
|
||||
return TextSpan(style: _style.baseStyle, children: formattedText);
|
||||
} else {
|
||||
// Parsing failed, return with only basic formatting
|
||||
return TextSpan(style: _style.baseStyle, text: src);
|
||||
}
|
||||
}
|
||||
|
||||
bool _generateSpans() {
|
||||
int lastLoopPosition = _scanner.position;
|
||||
|
||||
while (!_scanner.isDone) {
|
||||
// Skip White space
|
||||
_scanner.scan(RegExp(r'\s+'));
|
||||
|
||||
// Block comments
|
||||
if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.comment,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Line comments
|
||||
if (_scanner.scan(RegExp(r'//'))) {
|
||||
final int startComment = _scanner.lastMatch?.start??0;
|
||||
bool eof = false;
|
||||
int endComment;
|
||||
if (_scanner.scan(RegExp(r'(.*\r\n)|(.*\n)'))) {
|
||||
int? end = _scanner.lastMatch?.end;
|
||||
endComment = end==null?0:end - 1;
|
||||
} else {
|
||||
eof = true;
|
||||
endComment = _src.length;
|
||||
}
|
||||
|
||||
_spans.add(_HighlightSpan(_HighlightType.comment, startComment, endComment));
|
||||
|
||||
if (eof) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Raw r"String"
|
||||
if (_scanner.scan(RegExp(r'r".*"'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Raw r'String'
|
||||
if (_scanner.scan(RegExp(r"r'.*'"))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multiline """String"""
|
||||
if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multiline '''String'''
|
||||
if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// "String"
|
||||
if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 'String'
|
||||
if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Double
|
||||
if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.number,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Integer
|
||||
if (_scanner.scan(RegExp(r'\d+'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.number,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Punctuation
|
||||
if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.punctuation,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Meta data
|
||||
if (_scanner.scan(RegExp(r'@\w+'))) {
|
||||
_spans.add(_HighlightSpan(_HighlightType.keyword,
|
||||
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Words
|
||||
if (_scanner.scan(RegExp(r'\w+'))) {
|
||||
_HighlightType? type;
|
||||
String word = _scanner.lastMatch?[0]??'';
|
||||
|
||||
if (word.startsWith('_')) word = word.substring(1);
|
||||
|
||||
if (language.containsKeywords(word)) {
|
||||
type = _HighlightType.keyword;
|
||||
} else if (language.containsInTypes(word)) {
|
||||
type = _HighlightType.keyword;
|
||||
} else if (_firstLetterIsUpperCase(word)) {
|
||||
type = _HighlightType.klass;
|
||||
} else if (word.length >= 2 &&
|
||||
word.startsWith('k') &&
|
||||
_firstLetterIsUpperCase(word.substring(1))) {
|
||||
type = _HighlightType.constant;
|
||||
}
|
||||
if (type != null) {
|
||||
_spans.add(_HighlightSpan(
|
||||
type, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
||||
}
|
||||
}
|
||||
// Check if this loop did anything
|
||||
if (lastLoopPosition == _scanner.position) {
|
||||
// Failed to parse this file, abort gracefully
|
||||
return false;
|
||||
}
|
||||
lastLoopPosition = _scanner.position;
|
||||
}
|
||||
|
||||
_simplify();
|
||||
return true;
|
||||
}
|
||||
|
||||
void _simplify() {
|
||||
for (int i = _spans.length - 2; i >= 0; i -= 1) {
|
||||
if (_spans[i].type == _spans[i + 1].type && _spans[i].end == _spans[i + 1].start) {
|
||||
_spans[i] = _HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end);
|
||||
_spans.removeAt(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _firstLetterIsUpperCase(String str) {
|
||||
if (str.isNotEmpty) {
|
||||
final String first = str.substring(0, 1);
|
||||
return first == first.toUpperCase();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum _HighlightType {
|
||||
number,
|
||||
comment,
|
||||
keyword,
|
||||
string,
|
||||
punctuation,
|
||||
klass,
|
||||
constant
|
||||
}
|
||||
|
||||
class _HighlightSpan {
|
||||
_HighlightSpan(this.type, this.start, this.end);
|
||||
|
||||
final _HighlightType type;
|
||||
final int start;
|
||||
final int end;
|
||||
|
||||
String textForSpan(String src) {
|
||||
return src.substring(start, end);
|
||||
}
|
||||
|
||||
TextStyle? textStyle(HighlighterStyle? style) {
|
||||
if (type == _HighlightType.number) {
|
||||
return style?.numberStyle;
|
||||
} else if (type == _HighlightType.comment) {
|
||||
return style?.commentStyle;
|
||||
} else if (type == _HighlightType.keyword) {
|
||||
return style?.keywordStyle;
|
||||
} else if (type == _HighlightType.string) {
|
||||
return style?.stringStyle;
|
||||
} else if (type == _HighlightType.punctuation) {
|
||||
return style?.punctuationStyle;
|
||||
} else if (type == _HighlightType.klass) {
|
||||
return style?.classStyle;
|
||||
} else if (type == _HighlightType.constant) {
|
||||
return style?.constantStyle;
|
||||
} else {
|
||||
return style?.baseStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/// create by 张风捷特烈 on 2020-04-15
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020-04-11
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
class HighlighterStyle {
|
||||
//句法高亮样式
|
||||
const HighlighterStyle(
|
||||
{ //构造函数
|
||||
this.baseStyle, //基础样式
|
||||
this.numberStyle, //数字的样式
|
||||
this.commentStyle, //注释样式
|
||||
this.keywordStyle, //关键字样式
|
||||
this.stringStyle, //字符串样式
|
||||
this.punctuationStyle, //标点符号样式
|
||||
this.classStyle, //类名
|
||||
this.backgroundColor,
|
||||
this.constantStyle});
|
||||
|
||||
static List<int> get lightColor => [
|
||||
0xFF000000, //基础
|
||||
0xFF00b0e8, //数字
|
||||
0xFF9E9E9E, //注释
|
||||
0xFF9C27B0, //关键
|
||||
0xFF43A047, //字符串
|
||||
0xFF000000, //标点符号
|
||||
0xFF3D62F5, //类名
|
||||
0xFF795548, //常量
|
||||
0xffF6F8FA, //背景
|
||||
];
|
||||
|
||||
static List<int> get darkColor => [
|
||||
0xFFFFFFFF, //基础
|
||||
0xFFDF935F, //数字
|
||||
0xFF9E9E9E, //注释
|
||||
0xFF80CBC4, //关键字
|
||||
0xFFB9CA4A, //字符串
|
||||
0xFFFFFFFF, //标点符号
|
||||
0xFF7AA6DA, //类名
|
||||
0xFF795548, //常量
|
||||
0xFF1D1F21, //背景
|
||||
];
|
||||
|
||||
static List<int> get gitHub =>
|
||||
[
|
||||
0xFF333333, //基础
|
||||
0xFF008081, //数字
|
||||
0xFF9D9D8D, //注释
|
||||
0xFF009999, //关键字
|
||||
0xFFDD1045, //字符串
|
||||
0xFF333333, //标点符号
|
||||
0xFF6F42C1, //类名
|
||||
0xFF795548, //常量
|
||||
0xFFF8F8F8, //背景
|
||||
];
|
||||
|
||||
static List<int> get zenburn => [
|
||||
0xFFDCDCDC, //普通字
|
||||
0xFF87C5C8, //数字
|
||||
0xFF8F8F8F, //注释
|
||||
0xFFE4CEAB, //关键字
|
||||
0xFFCC9493, //字符串
|
||||
0xFFDCDCDC, //标点符号
|
||||
0xFFEFEF90, //类名
|
||||
0xFFEF5350, //常量
|
||||
0xFF3F3F3F, //背景
|
||||
];
|
||||
|
||||
static List<int> get mf =>[
|
||||
0xFF707D95, //基础
|
||||
0xFF6897BB, //数字
|
||||
0xFF629755, //注释
|
||||
0xFFCC7832, //关键
|
||||
0xFFF14E9F, //字符串
|
||||
0xFFFFBB33, //标点符号
|
||||
0xFF66CCFF, //类名
|
||||
0xFF9876AA, //常量
|
||||
0xFF2B2B2B //背景
|
||||
];
|
||||
|
||||
static List<int> get solarized =>[
|
||||
0xFF657B83, // 普通字
|
||||
0xFFD33682, // 数字
|
||||
0xFF93A1A1, // 注释
|
||||
0xFF859900, // 关键字
|
||||
0xFF2AA198, // 字符串
|
||||
0xFF859900, // 标点符号
|
||||
0xFF268BD2, // 类名
|
||||
0xFF268BD2, //常量
|
||||
0xFFDDD6C1, // 背景
|
||||
];
|
||||
|
||||
factory HighlighterStyle.fromColors(List<int> colors) => HighlighterStyle(
|
||||
baseStyle: TextStyle(
|
||||
color: Color(colors[0]),
|
||||
),
|
||||
numberStyle: TextStyle(
|
||||
color: Color(colors[1]),
|
||||
),
|
||||
commentStyle: TextStyle(
|
||||
color: Color(
|
||||
colors[2],
|
||||
),
|
||||
fontStyle: FontStyle.italic),
|
||||
keywordStyle: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(
|
||||
colors[3],
|
||||
),
|
||||
),
|
||||
stringStyle: TextStyle(
|
||||
color: Color(colors[4]),
|
||||
),
|
||||
punctuationStyle: TextStyle(
|
||||
color: Color(colors[5]),
|
||||
),
|
||||
classStyle: TextStyle(
|
||||
color: Color(colors[6]),
|
||||
),
|
||||
constantStyle: TextStyle(
|
||||
color: Color(colors[7]),
|
||||
),
|
||||
backgroundColor: Color(colors[8]),
|
||||
);
|
||||
final TextStyle? baseStyle;
|
||||
final TextStyle? numberStyle;
|
||||
final TextStyle? commentStyle;
|
||||
final TextStyle? keywordStyle;
|
||||
final TextStyle? stringStyle;
|
||||
final TextStyle? punctuationStyle;
|
||||
final TextStyle? classStyle;
|
||||
final TextStyle? constantStyle;
|
||||
final Color? backgroundColor;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
import 'language.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2021/1/21
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
class DartLanguage extends Language{
|
||||
|
||||
const DartLanguage() : super('Dart');
|
||||
|
||||
static const List<String> _kDartKeywords = [
|
||||
'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch',
|
||||
'class', 'const', 'continue', 'default', 'deferred', 'do', 'dynamic', 'else',
|
||||
'enum', 'export', 'external', 'extends', 'factory', 'false', 'final',
|
||||
'finally', 'for', 'get', 'if', 'implements', 'import', 'in', 'is', 'library',
|
||||
'new', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static',
|
||||
'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var',
|
||||
'void', 'while', 'with', 'yield'
|
||||
];
|
||||
|
||||
static const List<String> _kDartInTypes = [
|
||||
'int', 'double', 'num', 'bool'
|
||||
];
|
||||
|
||||
@override
|
||||
List<String> get keywords => _kDartKeywords;
|
||||
|
||||
@override
|
||||
List<String> get inTypes => <String>[
|
||||
'int', 'double', 'num', 'bool'
|
||||
];
|
||||
|
||||
@override
|
||||
bool containsInTypes(String word) =>_kDartKeywords.contains(word);
|
||||
|
||||
@override
|
||||
bool containsKeywords(String word)=>_kDartInTypes.contains(word);
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/// create by 张风捷特烈 on 2021/1/21
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
abstract class Language {
|
||||
final String name;
|
||||
|
||||
const Language(this.name);
|
||||
|
||||
bool containsKeywords(String word);
|
||||
|
||||
bool containsInTypes(String word);
|
||||
|
||||
List<String> get keywords;
|
||||
|
||||
List<String> get inTypes;
|
||||
}
|
||||
44
packages/components/lib/toly_ui/code_view/code_view.dart
Normal file
44
packages/components/lib/toly_ui/code_view/code_view.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'code/code_widget.dart';
|
||||
import 'code/highlighter_style.dart';
|
||||
|
||||
class CodeView extends StatefulWidget {
|
||||
final String path;
|
||||
const CodeView({super.key, required this.path});
|
||||
|
||||
@override
|
||||
State<CodeView> createState() => _CodeViewState();
|
||||
}
|
||||
|
||||
class _CodeViewState extends State<CodeView> {
|
||||
|
||||
String? content;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
_loadContent();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Material(
|
||||
child: CodeWidget(code:'${content}', style: HighlighterStyle.fromColors(HighlighterStyle.lightColor),),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _loadContent() async{
|
||||
content = await rootBundle.loadString(widget.path);
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
93
packages/components/lib/toly_ui/decoration/title.dart
Normal file
93
packages/components/lib/toly_ui/decoration/title.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TolyTitle extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color? lineColor;
|
||||
final Color? color;
|
||||
const TolyTitle({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.lineColor,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: TitleDecoration(
|
||||
color,lineColor,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TitleDecoration extends Decoration {
|
||||
final Color? lineColor;
|
||||
final Color? color;
|
||||
|
||||
const TitleDecoration( this.color,this.lineColor,);
|
||||
|
||||
@override
|
||||
BoxPainter createBoxPainter([VoidCallback? onChanged]) =>
|
||||
TitlePainter(color: color, lineColor: lineColor);
|
||||
}
|
||||
|
||||
class TitlePainter extends BoxPainter {
|
||||
final Color? color;
|
||||
final Color? lineColor;
|
||||
|
||||
const TitlePainter({this.color, this.lineColor});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
||||
canvas.save();
|
||||
canvas.translate(offset.dx, offset.dy);
|
||||
Size size = configuration.size ?? Size.zero;
|
||||
final Paint paint = Paint()
|
||||
// ..style = PaintingStyle.stroke
|
||||
..color = color??Colors.transparent
|
||||
..strokeWidth = 1;
|
||||
|
||||
|
||||
final Rect zone = Rect.fromCenter(
|
||||
center: Offset(size.width / 2, size.height / 2),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
);
|
||||
|
||||
canvas.drawRect(zone, paint);
|
||||
final Paint paint2 = Paint()
|
||||
..strokeWidth = 1
|
||||
// ..style = PaintingStyle.stroke
|
||||
// ..color = const Color(0xffFFFAA7);
|
||||
..color = lineColor??Colors.transparent;
|
||||
|
||||
const double start = 4;
|
||||
canvas.drawLine(const Offset(0, start), Offset(size.width, start), paint2);
|
||||
double end = size.height - 4;
|
||||
canvas.drawLine(Offset(0, end), Offset(size.width, end), paint2);
|
||||
|
||||
canvas.drawCircle(Offset(10,size.height/2), 4, paint2);
|
||||
|
||||
//
|
||||
// canvas.translate(
|
||||
// offset.dx + (configuration.size?.width??0) / 2,
|
||||
// offset.dy + (configuration.size?.height??0) / 2,
|
||||
// );
|
||||
//
|
||||
// final Rect zone = Rect.fromCenter(
|
||||
// center: Offset.zero,
|
||||
// width: configuration.size.width,
|
||||
// height: configuration.size.height,
|
||||
// );
|
||||
//
|
||||
// path.addRRect(RRect.fromRectAndRadius(
|
||||
// zone,
|
||||
// Radius.circular(20),
|
||||
// ));
|
||||
//
|
||||
// const DashPainter(span: 4, step: 9).paint(canvas, path, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
21
packages/components/lib/toly_ui/navigation/menu_meta.dart
Normal file
21
packages/components/lib/toly_ui/navigation/menu_meta.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class MenuMeta {
|
||||
// 标签
|
||||
final String label;
|
||||
|
||||
final String? path;
|
||||
|
||||
// 图标数据
|
||||
final IconData icon;
|
||||
|
||||
// 图标颜色
|
||||
final Color? color;
|
||||
|
||||
const MenuMeta({
|
||||
required this.label,
|
||||
this.path,
|
||||
required this.icon,
|
||||
this.color,
|
||||
});
|
||||
}
|
||||
102
packages/components/lib/toly_ui/navigation/toly_breadcrumb.dart
Normal file
102
packages/components/lib/toly_ui/navigation/toly_breadcrumb.dart
Normal file
@@ -0,0 +1,102 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/gestures/events.dart';
|
||||
|
||||
class TolyBreadcrumb extends StatelessWidget {
|
||||
final List<BreadcrumbItem> items;
|
||||
final double fontSize;
|
||||
final ValueChanged<BreadcrumbItem>? onTapItem;
|
||||
|
||||
const TolyBreadcrumb({super.key, required this.items, this.onTapItem, this.fontSize=14});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> children = [];
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
children.add(TolyBreadcrumbItem(
|
||||
fontSize: fontSize,
|
||||
item: items[i], onTapItem: onTapItem,
|
||||
));
|
||||
if (i != items.length - 1) {
|
||||
children.add(Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Text(
|
||||
'/',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return Wrap(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TolyBreadcrumbItem extends StatefulWidget {
|
||||
final BreadcrumbItem item;
|
||||
final double fontSize;
|
||||
|
||||
final ValueChanged<BreadcrumbItem>? onTapItem;
|
||||
const TolyBreadcrumbItem({super.key, required this.item, required this.onTapItem, required this.fontSize});
|
||||
|
||||
@override
|
||||
State<TolyBreadcrumbItem> createState() => _TolyBreadcrumbItemState();
|
||||
}
|
||||
|
||||
class _TolyBreadcrumbItemState extends State<TolyBreadcrumbItem> {
|
||||
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool hasTarget = (widget.item.to != null);
|
||||
Color? color = (_hover&&hasTarget)?Colors.blue:null;
|
||||
MouseCursor cursor = hasTarget?SystemMouseCursors.click:SystemMouseCursors.basic;
|
||||
|
||||
if(widget.item.active) {
|
||||
color = null;
|
||||
cursor = SystemMouseCursors.basic;
|
||||
}
|
||||
|
||||
TextStyle style = TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
fontWeight: hasTarget ? FontWeight.bold : null,
|
||||
color: color
|
||||
);
|
||||
|
||||
return MouseRegion(
|
||||
cursor: cursor,
|
||||
onEnter: _onEnter,
|
||||
onExit: _onExit,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if(!widget.item.active){
|
||||
widget.onTapItem?.call(widget.item);
|
||||
}
|
||||
},
|
||||
child: Text(widget.item.label, style: style)),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_hover = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class BreadcrumbItem {
|
||||
final String? to;
|
||||
final String label;
|
||||
final bool active;
|
||||
|
||||
BreadcrumbItem( {this.to, required this.label,this.active=false,});
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'menu_meta.dart';
|
||||
|
||||
class TolyNavigationRail extends StatelessWidget {
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Color backgroundColor;
|
||||
final int? selectedIndex;
|
||||
final Widget? leading;
|
||||
final Widget? tail;
|
||||
final List<MenuMeta> items;
|
||||
const TolyNavigationRail({
|
||||
Key? key,
|
||||
required this.onDestinationSelected,
|
||||
required this.selectedIndex,
|
||||
required this.items,
|
||||
this.leading,
|
||||
this.tail,
|
||||
required this.backgroundColor,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 64,
|
||||
color: backgroundColor,
|
||||
child: Column(
|
||||
children: [
|
||||
if(leading!=null) leading!,
|
||||
Expanded(
|
||||
child: LeftNavigationMenu(
|
||||
items: items,
|
||||
selectedIndex: selectedIndex,
|
||||
onTapItem: onDestinationSelected,
|
||||
)),
|
||||
if(tail!=null) tail!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftNavigationMenu extends StatelessWidget {
|
||||
final List<MenuMeta> items;
|
||||
final ValueChanged<int> onTapItem;
|
||||
|
||||
final int? selectedIndex;
|
||||
|
||||
const LeftNavigationMenu(
|
||||
{Key? key,
|
||||
required this.items,
|
||||
required this.selectedIndex,
|
||||
required this.onTapItem})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: items.asMap().keys.map((int index) {
|
||||
return LeftNavigationBarItemWidget(
|
||||
item: items[index],
|
||||
selected: index == selectedIndex,
|
||||
onTap: () {
|
||||
onTapItem.call(index);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftNavigationBarItemWidget extends StatefulWidget {
|
||||
final MenuMeta item;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const LeftNavigationBarItemWidget(
|
||||
{Key? key,
|
||||
required this.item,
|
||||
required this.selected,
|
||||
required this.onTap})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<LeftNavigationBarItemWidget> createState() =>
|
||||
_LeftNavigationBarItemWidgetState();
|
||||
}
|
||||
|
||||
class _LeftNavigationBarItemWidgetState
|
||||
extends State<LeftNavigationBarItemWidget> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color? bgColor;
|
||||
Color iconColor;
|
||||
if (widget.selected) {
|
||||
bgColor = Colors.white.withOpacity(0.2);
|
||||
iconColor = Colors.white;
|
||||
} else {
|
||||
bgColor = _hover ? Colors.white.withOpacity(0.1) : null;
|
||||
iconColor = Colors.white.withOpacity(0.8);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: widget.selected
|
||||
? null
|
||||
: () {
|
||||
widget.onTap();
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
},
|
||||
onHover: widget.selected
|
||||
? null
|
||||
: (v) {
|
||||
setState(() {
|
||||
_hover = v;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.only(bottom: 10),
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor, borderRadius: BorderRadius.circular(8)),
|
||||
child: Wrap(
|
||||
direction: Axis.vertical,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 2,
|
||||
children: [
|
||||
Icon(
|
||||
widget.item.icon,
|
||||
color: iconColor,
|
||||
),
|
||||
Text(
|
||||
widget.item.label,
|
||||
style: TextStyle(color: iconColor, fontSize: 12),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnDropSelected = void Function(int index);
|
||||
|
||||
class DropSelectableWidget extends StatefulWidget {
|
||||
final List<String> data;
|
||||
final OnDropSelected? onDropSelected;
|
||||
final Color disableColor;
|
||||
final double iconSize;
|
||||
final double height;
|
||||
final double width;
|
||||
final double fontSize;
|
||||
final String value;
|
||||
|
||||
const DropSelectableWidget(
|
||||
{Key? key,
|
||||
this.data = const [],
|
||||
this.onDropSelected,
|
||||
this.disableColor = Colors.black,
|
||||
this.iconSize = 24,
|
||||
required this.value,
|
||||
this.height = 30,
|
||||
this.width = 200,
|
||||
this.fontSize = 14,
|
||||
})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DropSelectableWidgetState createState() => _DropSelectableWidgetState();
|
||||
}
|
||||
|
||||
class _DropSelectableWidgetState extends State<DropSelectableWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late FocusNode _node;
|
||||
bool _focused = false;
|
||||
late FocusAttachment _nodeAttachment;
|
||||
OverlayEntry? _overlayEntry;
|
||||
late AnimationController _ctrl;
|
||||
late Animation<double> animation;
|
||||
final LayerLink layerLink = LayerLink();
|
||||
|
||||
// int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_ctrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
|
||||
animation = Tween<double>(begin: 0, end: pi).animate(_ctrl);
|
||||
_node = FocusNode()
|
||||
..addListener(() {
|
||||
if (_node.hasFocus != _focused) {
|
||||
if (!_focused) {
|
||||
_ctrl.forward();
|
||||
_showOverlay();
|
||||
} else {
|
||||
_hideOverlay();
|
||||
_ctrl.reverse();
|
||||
}
|
||||
setState(() {
|
||||
_focused = _node.hasFocus;
|
||||
});
|
||||
}
|
||||
});
|
||||
_nodeAttachment = _node.attach(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_node.dispose();
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_nodeAttachment.reparent();
|
||||
return TapRegion(
|
||||
groupId: 'selector',
|
||||
onTapOutside: (_){
|
||||
_node.unfocus();
|
||||
},
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_focused) {
|
||||
_node.unfocus();
|
||||
} else {
|
||||
_node.requestFocus();
|
||||
}
|
||||
},
|
||||
child: CompositedTransformTarget(
|
||||
link: layerLink,
|
||||
child: buildTarget(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showOverlay() {
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
void _hideOverlay() {
|
||||
_overlayEntry?.remove();
|
||||
}
|
||||
|
||||
Widget buildTarget() {
|
||||
return Container(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: _focused ? Colors.blue : widget.disableColor,
|
||||
)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text( widget.value ,style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: widget.fontSize
|
||||
),),
|
||||
AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, child) => Transform.rotate(
|
||||
angle: animation.value,
|
||||
child: child,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: widget.iconSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
OverlayEntry _createOverlayEntry() => OverlayEntry(
|
||||
builder: (BuildContext context) => UnconstrainedBox(
|
||||
child: CompositedTransformFollower(
|
||||
link: layerLink,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
followerAnchor: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Material(
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide.none,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5))),
|
||||
elevation: 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Container(
|
||||
height: 200,
|
||||
// alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xffDAE3FF),
|
||||
),
|
||||
// padding: const EdgeInsets.only(top: 5),
|
||||
width: widget.width,
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
// shrinkWrap: true,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: _buildItem),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
|
||||
bool active = widget.data[index] == widget.value;
|
||||
return TapRegion(
|
||||
groupId: 'selector',
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (!active) widget.onDropSelected?.call(index);
|
||||
_overlayEntry?.markNeedsBuild();
|
||||
_node.unfocus();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: active ? Colors.blue.withOpacity(0.2) : Colors.transparent,
|
||||
child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
117
packages/components/lib/toly_ui/popable/pop_menu.dart
Normal file
117
packages/components/lib/toly_ui/popable/pop_menu.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'toly_pop_menu.dart';
|
||||
|
||||
class PopPanel<T> extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Size size;
|
||||
final Offset offset;
|
||||
|
||||
/// Builds the context menu.
|
||||
final Widget panel;
|
||||
|
||||
const PopPanel({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.panel,
|
||||
this.offset = Offset.zero,
|
||||
this.size = const Size(250, 0),
|
||||
});
|
||||
|
||||
@override
|
||||
State<PopPanel> createState() => _PopPanelState();
|
||||
}
|
||||
|
||||
class _PopPanelState<T> extends State<PopPanel<T>> {
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _show() {
|
||||
TolyPopupMenuEntry<T> item = CustomTolyMenuItem(
|
||||
widget.panel,
|
||||
size: widget.size,
|
||||
);
|
||||
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay =
|
||||
Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final Offset offset = Offset(button.size.width / 2, button.size.height)+widget.offset;
|
||||
final RelativeRect position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(offset, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero) + offset,
|
||||
ancestor: overlay),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
showTolyMenu<T?>(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
context: context,
|
||||
items: [item],
|
||||
position: position,
|
||||
).then<void>((T? newValue) {
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
if (newValue == null) {
|
||||
// widget.onCanceled?.call();
|
||||
return null;
|
||||
}
|
||||
// widget.onSelected?.call(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
_show();
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTolyMenuItem<T> extends TolyPopupMenuEntry<T> {
|
||||
final Size size;
|
||||
final Widget content;
|
||||
|
||||
const CustomTolyMenuItem(this.content, {super.key, required this.size});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _CustomTolyMenuItemState();
|
||||
|
||||
@override
|
||||
// TODO: implement height
|
||||
double get height => kMinInteractiveDimension;
|
||||
|
||||
@override
|
||||
bool represents(value) => true;
|
||||
}
|
||||
|
||||
class _CustomTolyMenuItemState<T> extends State<CustomTolyMenuItem> {
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CustomTolyMenuItem oldWidget) {
|
||||
print('============_CustomTolyMenuItemState#didUpdateWidget');
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: widget.size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 6)
|
||||
]),
|
||||
// padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: widget.content,
|
||||
// color: Colors.lightBlueAccent,
|
||||
);
|
||||
}
|
||||
}
|
||||
52
packages/components/lib/toly_ui/popable/popover.dart
Normal file
52
packages/components/lib/toly_ui/popable/popover.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A builder that includes an Offset to draw the context menu at.
|
||||
typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset);
|
||||
|
||||
|
||||
class Popover extends StatefulWidget {
|
||||
final Widget child;
|
||||
/// Builds the context menu.
|
||||
final ContextMenuBuilder contextMenuBuilder;
|
||||
|
||||
const Popover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.contextMenuBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Popover> createState() => _PopoverState();
|
||||
}
|
||||
|
||||
class _PopoverState extends State<Popover> {
|
||||
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _show(Offset position) {
|
||||
_contextMenuController.show(
|
||||
context: context,
|
||||
contextMenuBuilder: (BuildContext context) {
|
||||
return widget.contextMenuBuilder(context, position);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final double width = 200;
|
||||
|
||||
void _onTap() {
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final Offset offset = Offset(button.size.width / 2, button.size.height);
|
||||
Offset boxOffset = button.localToGlobal(offset, ancestor: overlay);
|
||||
_show(boxOffset.translate(-width, 0));
|
||||
}
|
||||
}
|
||||
1433
packages/components/lib/toly_ui/popable/toly_pop_menu.dart
Normal file
1433
packages/components/lib/toly_ui/popable/toly_pop_menu.dart
Normal file
File diff suppressed because it is too large
Load Diff
8
packages/components/lib/toly_ui/toly_ui.dart
Normal file
8
packages/components/lib/toly_ui/toly_ui.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
export 'navigation/menu_meta.dart';
|
||||
export 'navigation/toly_breadcrumb.dart';
|
||||
export 'navigation/toly_navigation_rail.dart';
|
||||
export 'popable/drop_selectable_widget.dart';
|
||||
export 'popable/popover.dart';
|
||||
export 'popable/pop_menu.dart';
|
||||
export 'code_view/code_view.dart';
|
||||
24
packages/components/lib/windows/drag_to_move_area.dart
Normal file
24
packages/components/lib/windows/drag_to_move_area.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class DragToMoveWrap extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const DragToMoveWrap({
|
||||
Key? key,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if(kIsWeb) return child;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPanStart: (details) {
|
||||
windowManager.startDragging();
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
80
packages/components/lib/windows/window_buttons.dart
Normal file
80
packages/components/lib/windows/window_buttons.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class WindowButtons extends StatefulWidget {
|
||||
final List<Widget>? actions;
|
||||
const WindowButtons({Key? key, this.actions}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<WindowButtons> createState() => _WindowButtonsState();
|
||||
}
|
||||
|
||||
class _WindowButtonsState extends State<WindowButtons> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Brightness brightness = Theme.of(context).brightness;
|
||||
return Align(
|
||||
alignment:Alignment.topRight,child: Wrap(
|
||||
spacing: 5,
|
||||
children: [
|
||||
if(widget.actions!=null)
|
||||
...widget.actions!,
|
||||
SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: WindowCaptionButton.minimize(
|
||||
brightness:brightness,
|
||||
onPressed: () async {
|
||||
bool isMinimized = await windowManager.isMinimized();
|
||||
if (isMinimized) {
|
||||
windowManager.restore();
|
||||
} else {
|
||||
windowManager.minimize();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: FutureBuilder<bool>(
|
||||
future: windowManager.isMaximized(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.data == true) {
|
||||
return WindowCaptionButton.unmaximize(
|
||||
brightness: brightness,
|
||||
onPressed: () async{
|
||||
await windowManager.unmaximize();
|
||||
setState(() {
|
||||
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
return WindowCaptionButton.maximize(
|
||||
brightness: brightness,
|
||||
onPressed: () async{
|
||||
await windowManager.maximize();
|
||||
setState(() {
|
||||
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
width: 30,
|
||||
child: WindowCaptionButton.close(
|
||||
brightness: brightness,
|
||||
onPressed: () {
|
||||
windowManager.close();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,6 @@ class Coordinate {
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void _drawAxis(Canvas canvas, Size size) {
|
||||
_gridPaint
|
||||
..color = Colors.blue
|
||||
@@ -162,3 +160,4 @@ class Coordinate {
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class DemoShower extends StatefulWidget {
|
||||
final List<Widget> demos;
|
||||
const DemoShower({super.key, required this.demos});
|
||||
|
||||
@override
|
||||
State<DemoShower> createState() => _DemoShowerState();
|
||||
}
|
||||
|
||||
class _DemoShowerState extends State<DemoShower> {
|
||||
PageController _ctrl = PageController();
|
||||
int _index = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
PageView(
|
||||
controller: _ctrl,
|
||||
children: widget.demos,
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
TolyIconButton(
|
||||
onTap: (){
|
||||
_index= (_index-1)%widget.demos.length;
|
||||
setState(() {
|
||||
|
||||
});
|
||||
_ctrl.animateToPage(_index,curve: Curves.easeIn,duration: Duration(milliseconds: 200));
|
||||
},
|
||||
iconData: Icons.navigate_before,
|
||||
size: 36,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text('第 ${_index+1}/${widget.demos.length} 页',style: TextStyle(fontSize: 16,color: Colors.grey),),
|
||||
),
|
||||
|
||||
TolyIconButton(
|
||||
onTap: (){
|
||||
_index= (_index+1)%widget.demos.length;
|
||||
setState(() {
|
||||
|
||||
});
|
||||
_ctrl.animateToPage(_index,curve: Curves.easeIn,duration: Duration(milliseconds: 200));
|
||||
},
|
||||
size: 36,
|
||||
iconData: Icons.navigate_next,
|
||||
),
|
||||
|
||||
],
|
||||
)),
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TolyIconButton extends StatefulWidget {
|
||||
final IconData iconData;
|
||||
final VoidCallback onTap;
|
||||
final double size;
|
||||
const TolyIconButton({
|
||||
super.key,
|
||||
required this.iconData,
|
||||
required this.size, required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TolyIconButton> createState() => _TolyIconButtonState();
|
||||
}
|
||||
|
||||
class _TolyIconButtonState extends State<TolyIconButton> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: MouseRegion(
|
||||
onEnter: _onEnter,
|
||||
onExit: _onExit,
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
decoration: BoxDecoration(
|
||||
color: _hover?Colors.grey.withOpacity(0.6):Colors.grey.withOpacity(0.5), shape: BoxShape.circle),
|
||||
child: Icon(
|
||||
widget.iconData,
|
||||
size: 26,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_hover = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:components/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/gestures/events.dart';
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 's01_page.dart' as s1;
|
||||
import 's02_page.dart' as s2;
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
|
||||
class P01Page extends StatelessWidget {
|
||||
const P01Page({super.key});
|
||||
@@ -10,6 +9,7 @@ class P01Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p01',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -21,6 +21,7 @@ class P02Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p02',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -21,6 +21,7 @@ class P03Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p03',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
@@ -39,3 +40,19 @@ class P03Page extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -16,6 +16,7 @@ class P04Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p04',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -20,6 +20,7 @@ class P05Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p05',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -14,6 +14,7 @@ class P06Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p06',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -14,6 +14,7 @@ class P07Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p07',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -18,6 +18,8 @@ class P08Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p08',
|
||||
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -11,6 +11,8 @@ class P09Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p09',
|
||||
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -13,6 +13,8 @@ class P10Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p10',
|
||||
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:idraw/components/demo_shower.dart';
|
||||
import 'package:components/components.dart';
|
||||
import 's01.dart' as s1;
|
||||
import 's02.dart' as s2;
|
||||
import 's03.dart' as s3;
|
||||
@@ -12,6 +12,7 @@ class P11Page extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DemoShower(
|
||||
srcCodeDir: 'draw/p11',
|
||||
demos: [
|
||||
s1.Paper(),
|
||||
s2.Paper(),
|
||||
|
||||
@@ -80,7 +80,7 @@ class CurveBox extends StatefulWidget {
|
||||
final Color color;
|
||||
final Curve curve;
|
||||
|
||||
CurveBox({Key? key, this.color = Colors.lightBlue, this.curve = Curves.linear})
|
||||
const CurveBox({Key? key, this.color = Colors.lightBlue, this.curve = Curves.linear})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
||||
@@ -34,6 +34,12 @@ class _PaperState extends State<Paper> with SingleTickerProviderStateMixin {
|
||||
// _controller.repeat(reverse: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
|
||||
Reference in New Issue
Block a user