This commit is contained in:
toly
2023-12-16 12:40:32 +08:00
parent ab2778a22b
commit 01fdf966c5
593 changed files with 8995 additions and 27753 deletions

View 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';

View 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;
});
}
}

View File

@@ -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;
});
}
}

View 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';

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View 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(() {
});
}
}

View 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();
}
}

View 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,
});
}

View 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,});
}

View File

@@ -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),
)
],
),
),
);
}
}

View File

@@ -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),)),
),
),
);
}
}

View 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,
);
}
}

View 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));
}
}

File diff suppressed because it is too large Load Diff

View 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';

View 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,
);
}
}

View 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();
},
),
),
],
),
);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
});
}
}

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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 {
);
}
}

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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

View File

@@ -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(