intermediate
This commit is contained in:
@@ -5,5 +5,5 @@ export 'src/dashboard.dart';
|
||||
export 'src/elements/connection_params.dart';
|
||||
export 'src/elements/flow_element.dart';
|
||||
export 'src/flow_chart.dart';
|
||||
export 'src/ui/draw_arrow.dart' show ArrowParams, ArrowStyle;
|
||||
export 'src/ui/draw_arrow.dart' show ArrowParams, ArrowStyle, ArrowDirection;
|
||||
export 'src/ui/grid_background.dart' show GridBackgroundParams;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_flow_chart/src/flow_chart_left_menu.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -23,10 +24,15 @@ typedef ConnectionListener = void Function(
|
||||
//
|
||||
class Dashboard extends ChangeNotifier {
|
||||
GlobalKey<FlowChartSelectedMenuState> selectedMenuKey = GlobalKey<FlowChartSelectedMenuState>();
|
||||
GlobalKey<FlowChartLeftMenuState> selectedLeftMenuKey = GlobalKey<FlowChartLeftMenuState>();
|
||||
GlobalKey<FlowChartMenuState> chartMenuKey = GlobalKey<FlowChartMenuState>();
|
||||
GlobalKey<ChartWidgetState> chartKey = GlobalKey<ChartWidgetState>();
|
||||
List<Map<String, dynamic>> tempHistory = [];
|
||||
List<Map<String, dynamic>> history = [];
|
||||
Map<String, dynamic> scheduler = {};
|
||||
Map<String, bool> schedulerState = {};
|
||||
bool schedulerSave = false;
|
||||
String? id;
|
||||
String name;
|
||||
String defaultName = "";
|
||||
bool isMenu = true;
|
||||
@@ -39,11 +45,17 @@ class Dashboard extends ChangeNotifier {
|
||||
double defaultDashWidth = 0;
|
||||
double defaultBackWidth = 10;
|
||||
double defaultForwardWidth = 10;
|
||||
final void Function()? save;
|
||||
Future<void> Function(String? id)? save;
|
||||
List<Widget> Function(FlowData? obj)? infoItemWidget;
|
||||
List<Widget> Function()? infoWidget;
|
||||
FlowData? Function(Map<String, dynamic> json)? transformToData;
|
||||
///
|
||||
Dashboard({
|
||||
this.id,
|
||||
this.transformToData,
|
||||
required this.name,
|
||||
this.save,
|
||||
this.scheduler = const {},
|
||||
Offset? handlerFeedbackOffset,
|
||||
this.isMenu = true,
|
||||
this.defaultDashSpace = 0,
|
||||
@@ -53,6 +65,8 @@ class Dashboard extends ChangeNotifier {
|
||||
this.defaultArrowDirection = ArrowDirection.forward,
|
||||
this.defaultArrowStyle = ArrowStyle.curve,
|
||||
this.loadedGraph,
|
||||
this.infoWidget,
|
||||
this.load,
|
||||
}) : elements = [],
|
||||
_dashboardPosition = Offset.zero,
|
||||
dashboardSize = Size.zero,
|
||||
@@ -80,25 +94,28 @@ class Dashboard extends ChangeNotifier {
|
||||
addToHistory();
|
||||
}
|
||||
|
||||
Future<void> Function(String cat)? load;
|
||||
|
||||
///
|
||||
factory Dashboard.fromMap(Map<String, dynamic> map) {
|
||||
final d = Dashboard(
|
||||
name: map['name'] as String,
|
||||
isMenu: map['isMenu'] as bool,
|
||||
scheduler: map['schedule'] as Map<String, String>? ?? {},
|
||||
defaultDashSpace: map['defaultDashSpace'] as double? ?? 0,
|
||||
defaultDashWidth: map['defaultDashWidth'] as double? ?? 0,
|
||||
defaultArrowDirection: ArrowDirection.values[
|
||||
map['defaultArrowDirection'] as int? ?? 0],
|
||||
defaultArrowStyle: ArrowStyle.values[map['arrowStyle'] as int? ?? 0],
|
||||
)
|
||||
..arrows = List<ArrowPainter>.from(
|
||||
);
|
||||
d..arrows = List<ArrowPainter>.from(
|
||||
(map['arrows'] as List<dynamic>).map<ArrowPainter>(
|
||||
(x) => ArrowPainter.fromMap(x as Map<String, dynamic>),
|
||||
),
|
||||
)
|
||||
..elements = List<FlowElement>.from(
|
||||
(map['elements'] as List<dynamic>).map<FlowElement>(
|
||||
(x) => FlowElement.fromMap(x as Map<String, dynamic>),
|
||||
(x) => FlowElement.fromMap(d, x as Map<String, dynamic>),
|
||||
),
|
||||
)
|
||||
..dashboardSize = Size(
|
||||
@@ -120,6 +137,7 @@ class Dashboard extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void copyFromMap(Map<String, dynamic> map) {
|
||||
scheduler = map['schedule'] as Map<String, String>? ?? {};
|
||||
defaultArrowStyle = ArrowStyle.values[map['arrowStyle'] as int? ?? 0];
|
||||
defaultDashSpace = map['defaultDashSpace'] as double? ?? 0;
|
||||
defaultDashWidth = map['defaultDashWidth'] as double? ?? 0;
|
||||
@@ -132,7 +150,7 @@ class Dashboard extends ChangeNotifier {
|
||||
);
|
||||
elements = List<FlowElement>.from(
|
||||
(map['elements'] as List<dynamic>).map<FlowElement>(
|
||||
(x) => FlowElement.fromMap(x as Map<String, dynamic>),
|
||||
(x) => FlowElement.fromMap(this, x as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
dashboardSize = Size(
|
||||
@@ -148,7 +166,6 @@ class Dashboard extends ChangeNotifier {
|
||||
blockDefaultZoomGestures =
|
||||
(map['blockDefaultZoomGestures'] as bool? ?? false);
|
||||
minimumZoomFactor = map['minimumZoomFactor'] as double? ?? 0.25;
|
||||
if (save != null) { save!(); }
|
||||
}
|
||||
|
||||
///
|
||||
@@ -194,22 +211,34 @@ class Dashboard extends ChangeNotifier {
|
||||
final List<ConnectionListener> _connectionListeners = [];
|
||||
|
||||
Map<String, dynamic> serialize() {
|
||||
Map<String, dynamic> d = {};
|
||||
Map<String, dynamic> graph = {};
|
||||
graph['zoom'] = getZoomFactor();
|
||||
graph['elements'] = {};
|
||||
for(var el in elements) {
|
||||
graph['elements'][el.id] = el.serialize();
|
||||
}
|
||||
graph['arrows'] = arrows.map((e) => e.serialize()).toList();
|
||||
return graph;
|
||||
d["id"]=id;
|
||||
d["name"]=name;
|
||||
d["graph"]=graph;
|
||||
if (schedulerSave) {
|
||||
d["schedule"]=scheduler;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void deserialize(Map<String, dynamic> graph) {
|
||||
elements.clear();
|
||||
arrows.clear();
|
||||
for(var el in graph['elements'].values) {
|
||||
elements = [];
|
||||
arrows = [];
|
||||
print(graph['schedule']);
|
||||
scheduler = graph['schedule'] ?? {};
|
||||
setZoomFactor(graph["graph"]?["zoom"] ?? 1.0);
|
||||
for(var el in graph['graph']?['elements'] ?? []) {
|
||||
List<ConnectionParams> nexts = [];
|
||||
var flow = FlowElement.deserialize(el);
|
||||
for(var ar in graph['arrows']) {
|
||||
var flow = FlowElement.deserialize(this, el);
|
||||
for(var ar in graph['graph']['arrows']) {
|
||||
if (ar['from']['id'] != flow.id) { continue; }
|
||||
nexts.add(ConnectionParams(
|
||||
srcElementId: ar['from']['id'],
|
||||
destElementId: ar['to']['id'],
|
||||
@@ -221,7 +250,21 @@ class Dashboard extends ChangeNotifier {
|
||||
));
|
||||
}
|
||||
flow.next = nexts;
|
||||
List<FlowData> arr = [];
|
||||
for (var cat in chartKey.currentState?.widget.flowChart.widget.categories ?? []) {
|
||||
for (var build in chartKey.currentState!.widget.flowChart.widget.draggableItemBuilder(cat)) {
|
||||
arr.add(build);
|
||||
}
|
||||
}
|
||||
try {
|
||||
FlowData data = arr.firstWhere((element) => element.getID() == flow.element?.getID());
|
||||
flow.kind = ElementKind.widget;
|
||||
flow.widget = chartKey.currentState?.widget.flowChart.widget.itemWidget(data);
|
||||
} catch (e) { print(e); }
|
||||
elements.add(flow);
|
||||
}
|
||||
selectedMenuKey.currentState?.setState(() { });
|
||||
chartMenuKey.currentState?.setState(() { });
|
||||
}
|
||||
|
||||
/// add listener called when a new connection is created
|
||||
@@ -249,7 +292,7 @@ class Dashboard extends ChangeNotifier {
|
||||
void addToHistory() {
|
||||
if (tempHistory.length >= 50) { tempHistory.removeAt(0); }
|
||||
tempHistory.add(toMap());
|
||||
if (save != null) { save!(); }
|
||||
if (save != null) { save!(id); }
|
||||
history = tempHistory.map((e) => e).toList();
|
||||
chartKey.currentState?.setState(() { });
|
||||
chartMenuKey.currentState?.setState(() { });
|
||||
@@ -774,7 +817,7 @@ class Dashboard extends ChangeNotifier {
|
||||
|
||||
final loadedElements = List<FlowElement>.from(
|
||||
(source['elements'] as List<dynamic>).map<FlowElement>(
|
||||
(x) => FlowElement.fromMap(x as Map<String, dynamic>),
|
||||
(x) => FlowElement.fromMap(this, x as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
elements
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:flutter_flow_chart/src/elements/connection_params.dart';
|
||||
import 'package:flutter_flow_chart/src/dashboard.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Kinf od element
|
||||
@@ -61,12 +61,16 @@ enum Handler {
|
||||
}
|
||||
|
||||
/// Class to store [ElementWidget]s and notify its changes
|
||||
class FlowElement extends ChangeNotifier {
|
||||
class FlowElement<T extends FlowData> extends ChangeNotifier {
|
||||
Dashboard dashboard;
|
||||
bool isSelected = false;
|
||||
T? element;
|
||||
///
|
||||
FlowElement({
|
||||
required this.dashboard,
|
||||
Offset position = Offset.zero,
|
||||
String? id,
|
||||
this.element,
|
||||
this.size = Size.zero,
|
||||
this.text = '',
|
||||
this.textColor = Colors.black,
|
||||
@@ -103,8 +107,12 @@ class FlowElement extends ChangeNotifier {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
factory FlowElement.fromMap(Map<String, dynamic> map) {
|
||||
final e = FlowElement(
|
||||
factory FlowElement.fromMap(Dashboard dashboard, Map<String, dynamic> map) {
|
||||
final e = FlowElement<T>(
|
||||
element: (dashboard.transformToData != null
|
||||
? dashboard.transformToData!(map['element'] ?? {})
|
||||
: null) as T?,
|
||||
dashboard: dashboard,
|
||||
widget: map['widget'] as Widget?,
|
||||
size: Size(map['size.width'] as double, map['size.height'] as double),
|
||||
text: map['text'] as String,
|
||||
@@ -141,8 +149,8 @@ class FlowElement extends ChangeNotifier {
|
||||
|
||||
|
||||
///
|
||||
factory FlowElement.fromJson(String source) =>
|
||||
FlowElement.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
factory FlowElement.fromJson(Dashboard dashboard, String source) =>
|
||||
FlowElement.fromMap(dashboard, json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
/// Unique id set when adding a [FlowElement] with [Dashboard.addElement()]
|
||||
late String id;
|
||||
@@ -365,14 +373,18 @@ class FlowElement extends ChangeNotifier {
|
||||
graphElement['y'] = position.dy;
|
||||
graphElement['width'] = size.width;
|
||||
graphElement['height'] = size.height;
|
||||
graphElement['element']=element?.serialize();
|
||||
return graphElement;
|
||||
}
|
||||
|
||||
static FlowElement deserialize(Map<String, dynamic> map) {
|
||||
return FlowElement(
|
||||
id: map['id'],
|
||||
position: Offset(map['x'], map['y']),
|
||||
size: Size(map['width'], map['height']),
|
||||
);
|
||||
}
|
||||
static FlowElement deserialize<T extends FlowData>(Dashboard dashboard, Map<String, dynamic> map) {
|
||||
return FlowElement<T>(
|
||||
dashboard: dashboard,
|
||||
id: map['id'],
|
||||
kind: ElementKind.widget,
|
||||
position: Offset(double.parse("${map['x']}"), double.parse("${map['y']}")),
|
||||
size: Size(double.parse("${map['width']}"), double.parse("${map['height']}")),
|
||||
element: (dashboard.transformToData != null ? dashboard.transformToData!(map['element'] ?? {}) : null) as T?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// ignore: directives_ordering
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:flutter_flow_chart/src/dashboard.dart';
|
||||
import 'package:flutter_flow_chart/src/elements/flow_element.dart';
|
||||
import 'package:flutter_flow_chart/src/flow_chart_left_menu.dart';
|
||||
import 'package:flutter_flow_chart/src/flow_chart_menu.dart';
|
||||
import 'package:flutter_flow_chart/src/flow_chart_selected_menu.dart';
|
||||
import 'package:flutter_flow_chart/src/ui/draw_arrow.dart';
|
||||
@@ -15,7 +16,14 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Main flow chart Widget.
|
||||
/// It displays the background grid, all the elements and connection lines
|
||||
class FlowChart<T extends Object> extends StatefulWidget {
|
||||
abstract class FlowData {
|
||||
String getID();
|
||||
String getName();
|
||||
Map<String, dynamic> serialize();
|
||||
FlowData deserialize(Map<String, dynamic> data);
|
||||
}
|
||||
|
||||
class FlowChart<T extends FlowData> extends StatefulWidget {
|
||||
FlowChart({
|
||||
required this.dashboard,
|
||||
required this.itemWidget,
|
||||
@@ -44,13 +52,15 @@ class FlowChart<T extends Object> extends StatefulWidget {
|
||||
this.categories = const [],
|
||||
required this.draggableItemBuilder,
|
||||
this.onDashboardAlertOpened,
|
||||
this.menuExtension,
|
||||
}) {}
|
||||
|
||||
final List<String> categories;
|
||||
final double width;
|
||||
final double height;
|
||||
final double innerMenuWidth;
|
||||
|
||||
Widget Function()? menuExtension;
|
||||
|
||||
double itemWidth = 80;
|
||||
double zoom = 1;
|
||||
|
||||
@@ -161,7 +171,6 @@ class HoverMenuController {
|
||||
currentState?.hideSubMenu();
|
||||
}
|
||||
}
|
||||
bool isPopUp = false;
|
||||
class HoverMenu extends StatefulWidget {
|
||||
final Widget title;
|
||||
final double? width;
|
||||
@@ -277,49 +286,8 @@ class HoverMenuState extends State<HoverMenu> {
|
||||
}
|
||||
|
||||
var node = FocusNode();
|
||||
class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
List<Draggable<T>> getDraggable(List<T> items) {
|
||||
List<Draggable<T>> res = [];
|
||||
double realSize = widget.itemWidth * widget.zoom;
|
||||
GlobalKey<HoverMenuState> hoverKey = GlobalKey<HoverMenuState>();
|
||||
for (var e in items) {
|
||||
res.add(Draggable<T>(
|
||||
// Data is the value this Draggable stores.
|
||||
data: e,
|
||||
onDragStarted: () => hoverKey.currentState?.hideSubMenu(),
|
||||
onDragEnd: (d) => node.requestFocus(),
|
||||
childWhenDragging: Opacity(opacity: .5,
|
||||
child: Padding( padding: const EdgeInsets.all(10),
|
||||
child: Container( height: realSize - 20, child: widget.itemWidget(e) ))),
|
||||
feedback: Container( height: realSize, child: widget.itemWidget(e) ),
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.grab, child: Padding( padding: const EdgeInsets.all(10),
|
||||
child: widget.itemWidgetTooltip != null ? HoverMenu( key: hoverKey, width: 400, title: Container(
|
||||
height: realSize - 20, child: widget.itemWidget(e) ),
|
||||
items: [
|
||||
Container(child: widget.itemWidgetTooltip!(e)),
|
||||
]
|
||||
) : Container(
|
||||
height: realSize - 20, child: widget.itemWidget(e)
|
||||
)
|
||||
) )));
|
||||
}
|
||||
if (!widget.dashboard.isOpened && widget.onDashboardAlertOpened != null && isPopUp == false) {
|
||||
isPopUp = true;
|
||||
widget.dashboard.isOpened = true;
|
||||
Future.delayed(Duration(milliseconds: 1), () => showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
titlePadding: EdgeInsets.zero,
|
||||
insetPadding: EdgeInsets.zero,
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)),
|
||||
title: widget.onDashboardAlertOpened!(context, widget.dashboard));
|
||||
}));
|
||||
}
|
||||
widget.dashboard.isOpened = true;
|
||||
return res;
|
||||
}
|
||||
class FlowChartState<T extends FlowData> extends State<FlowChart> {
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -342,48 +310,14 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// disabling default browser context menu on web
|
||||
if (kIsWeb) BrowserContextMenu.disableContextMenu();
|
||||
|
||||
List<Widget> menuItems = [];
|
||||
for (var cat in widget.categories) {
|
||||
menuItems.add(
|
||||
Container( width: widget.dashboard.isMenu ? widget.innerMenuWidth : 0, margin: const EdgeInsets.only(bottom: 0.3),
|
||||
decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey, width: .5))),
|
||||
child: Stack( children: [
|
||||
widget.dashboard.isMenu && widget.innerMenuWidth < 200 ? Wrap( alignment: WrapAlignment.start,
|
||||
children: getDraggable(widget.draggableItemBuilder(cat) as List<T>))
|
||||
: ExpansionTile(
|
||||
shape: const ContinuousRectangleBorder(side: BorderSide(color: Colors.transparent)),
|
||||
initiallyExpanded: true,
|
||||
title: SizedBox(
|
||||
child : Row( children: [
|
||||
Padding(padding: const EdgeInsets.only(right: 10),
|
||||
child: Icon(cat.toUpperCase().contains("DATA") ? Icons.grid_on : Icons.bookmark, color: Colors.grey)),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text(cat.toUpperCase(), overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.black, fontSize: 11, fontWeight: FontWeight.w500))))
|
||||
])
|
||||
),
|
||||
iconColor: Colors.white,
|
||||
collapsedIconColor: Colors.white,
|
||||
children: [
|
||||
Container( width: widget.dashboard.isMenu ? widget.innerMenuWidth : 0, color: Colors.white,
|
||||
child : Wrap( alignment: WrapAlignment.start,
|
||||
children: getDraggable(widget.draggableItemBuilder(cat) as List<T>))
|
||||
)],
|
||||
)
|
||||
]
|
||||
))
|
||||
);
|
||||
}
|
||||
return KeyboardListener(
|
||||
focusNode: node,
|
||||
onKeyEvent: (event) {
|
||||
bool change = false;
|
||||
if ((event is KeyDownEvent || event is KeyRepeatEvent) && event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||
change = true;
|
||||
for (var el in widget.dashboard.elements) {
|
||||
if (el.isSelected) {
|
||||
el.position = Offset(el.position.dx, el.position.dy - 10);
|
||||
@@ -391,6 +325,7 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
}
|
||||
if ((event is KeyDownEvent || event is KeyRepeatEvent) && event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||
change = true;
|
||||
for (var el in widget.dashboard.elements) {
|
||||
if (el.isSelected) {
|
||||
el.position = Offset(el.position.dx, el.position.dy + 10);
|
||||
@@ -398,6 +333,7 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
}
|
||||
if ((event is KeyDownEvent || event is KeyRepeatEvent) && event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||
change = true;
|
||||
for (var el in widget.dashboard.elements) {
|
||||
if (el.isSelected) {
|
||||
el.position = Offset(el.position.dx - 10, el.position.dy);
|
||||
@@ -405,6 +341,7 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
}
|
||||
if ((event is KeyDownEvent || event is KeyRepeatEvent) && event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||
change = true;
|
||||
for (var el in widget.dashboard.elements) {
|
||||
if (el.isSelected) {
|
||||
el.position = Offset(el.position.dx + 10, el.position.dy);
|
||||
@@ -413,8 +350,11 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
|
||||
if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.add) {
|
||||
change = true;
|
||||
for (var el in widget.dashboard.elementSelected) {
|
||||
widget.dashboard.elements.add(FlowElement(
|
||||
widget.dashboard.elements.add(FlowElement<T>(
|
||||
element: el.element as T,
|
||||
dashboard: widget.dashboard,
|
||||
id: const Uuid().v4(),
|
||||
position: el.position + const Offset(100, 100),
|
||||
size: el.size,
|
||||
@@ -427,6 +367,7 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
}
|
||||
if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.delete) {
|
||||
change = true;
|
||||
widget.dashboard.elements.removeWhere( (el) => el.isSelected );
|
||||
for (var arrow in widget.dashboard.arrowsSelected) {
|
||||
for (var el in widget.dashboard.elements.where((element) => element.id == arrow.fromID.split("_")[0])) {
|
||||
@@ -435,11 +376,13 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
}
|
||||
widget.dashboard.arrows.removeWhere( (el) => el.isSelected );
|
||||
}
|
||||
DrawingArrow.instance.notifyListeners();
|
||||
widget.dashboard.chartKey.currentState?.setState(() { });
|
||||
Future.delayed(Duration(milliseconds: 10), () {
|
||||
node.requestFocus();
|
||||
});
|
||||
if (change) {
|
||||
DrawingArrow.instance.notifyListeners();
|
||||
widget.dashboard.chartKey.currentState?.setState(() { });
|
||||
/*Future.delayed(Duration(milliseconds: 10), () {
|
||||
node.requestFocus();
|
||||
});*/
|
||||
}
|
||||
},
|
||||
child: ClipRect(
|
||||
child: Stack(
|
||||
@@ -456,7 +399,7 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
return SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: ChartWidget(
|
||||
child: ChartWidget<T>(
|
||||
key: widget.dashboard.chartKey,
|
||||
flowChart: this,
|
||||
dashboard: widget.dashboard,
|
||||
@@ -478,8 +421,10 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
onAcceptWithDetails: (DragTargetDetails<T> details) {
|
||||
var e = details.data;
|
||||
String newID = const Uuid().v4();
|
||||
FlowElement el = FlowElement(
|
||||
FlowElement<T> el = FlowElement<T>(
|
||||
dashboard: widget.dashboard,
|
||||
id: newID,
|
||||
element: e,
|
||||
position: details.offset,
|
||||
size: const Size(100, 100),
|
||||
text: '${widget.dashboard.elements.length}',
|
||||
@@ -497,16 +442,70 @@ class FlowChartState<T extends Object> extends State<FlowChart> {
|
||||
},
|
||||
))]
|
||||
),
|
||||
widget.dashboard.isMenu ? Positioned(top: 50, child: Container(
|
||||
height: widget.height - 50,
|
||||
constraints: BoxConstraints(minWidth: widget.itemWidth),
|
||||
width: widget.dashboard.isMenu ? widget.innerMenuWidth : 0,
|
||||
color: Colors.grey.shade300,
|
||||
child: SingleChildScrollView( child: Column( children: menuItems ) )
|
||||
)) : Container(),
|
||||
widget.dashboard.isMenu ? Positioned(top: 50, child: FlowChartLeftMenu<T>(
|
||||
key: widget.dashboard.selectedLeftMenuKey,
|
||||
dashboard: widget.dashboard,
|
||||
categories: widget.categories,
|
||||
height: widget.height,
|
||||
innerMenuWidth: widget.innerMenuWidth,
|
||||
itemWidth: widget.itemWidth,
|
||||
menuExtension: widget.menuExtension,
|
||||
draggableItemBuilder: widget.draggableItemBuilder as List<T> Function(String cat),
|
||||
getDraggable: getDraggable,
|
||||
) )
|
||||
: Container(),
|
||||
widget.dashboard.isInfo ? Positioned(top: 50, right: 0, child:
|
||||
FlowChartSelectedMenu(
|
||||
key: widget.dashboard.selectedMenuKey,
|
||||
dashboard: widget.dashboard,
|
||||
height: MediaQuery.of(context).size.height - 100
|
||||
)
|
||||
) : Container()
|
||||
])
|
||||
));
|
||||
}
|
||||
|
||||
List<Draggable<T>> getDraggable(List<T> items) {
|
||||
List<Draggable<T>> res = [];
|
||||
double realSize = widget.itemWidth * widget.zoom;
|
||||
for (var e in items) {
|
||||
GlobalKey<HoverMenuState> hoverKey = GlobalKey<HoverMenuState>();
|
||||
res.add(Draggable<T>(
|
||||
// Data is the value this Draggable stores.
|
||||
data: e,
|
||||
onDragStarted: () => hoverKey.currentState?.hideSubMenu(),
|
||||
onDragEnd: (d) => node.requestFocus(),
|
||||
childWhenDragging: Opacity(opacity: .5,
|
||||
child: Padding( padding: const EdgeInsets.all(10),
|
||||
child: Container( height: realSize - 20, child: widget.itemWidget(e) ))),
|
||||
feedback: Container( height: realSize, child: widget.itemWidget(e) ),
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.grab, child: Padding( padding: const EdgeInsets.all(10),
|
||||
child: widget.itemWidgetTooltip != null ? HoverMenu( key: hoverKey, width: 400, title: Container(
|
||||
height: realSize - 20, child: widget.itemWidget(e) ),
|
||||
items: [
|
||||
Container(child: widget.itemWidgetTooltip!(e)),
|
||||
]
|
||||
) : Container(
|
||||
height: realSize - 20, child: widget.itemWidget(e)
|
||||
)
|
||||
) )));
|
||||
}
|
||||
if (!widget.dashboard.isOpened && widget.onDashboardAlertOpened != null ) {
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
titlePadding: EdgeInsets.zero,
|
||||
insetPadding: EdgeInsets.zero,
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)),
|
||||
title: widget.onDashboardAlertOpened!(context, widget.dashboard));
|
||||
|
||||
}); });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget to draw interactive connection when the user tap on handlers
|
||||
@@ -553,8 +552,7 @@ class _DrawingArrowWidgetState extends State<DrawingArrowWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
class ChartWidget extends StatefulWidget {
|
||||
|
||||
class ChartWidget<T extends FlowData> extends StatefulWidget {
|
||||
ChartWidget ({ Key? key,
|
||||
required this.flowChart,
|
||||
this.onElementPressed,
|
||||
@@ -667,9 +665,9 @@ class ChartWidget extends StatefulWidget {
|
||||
|
||||
final Dashboard dashboard;
|
||||
|
||||
@override ChartWidgetState createState() => ChartWidgetState();
|
||||
@override ChartWidgetState createState() => ChartWidgetState<T>();
|
||||
}
|
||||
class ChartWidgetState extends State<ChartWidget> {
|
||||
class ChartWidgetState<T extends FlowData> extends State<ChartWidget> {
|
||||
bool hoverImportant = false;
|
||||
final segmentedTension = ValueNotifier<double>(1);
|
||||
|
||||
@@ -710,8 +708,7 @@ class ChartWidgetState extends State<ChartWidget> {
|
||||
var secondaryTapDownPos = Offset.zero;
|
||||
for (int i = 0; i < widget.dashboard.elements.length; i++)
|
||||
widget.dashboard.elements[i].next.removeWhere((element) =>
|
||||
widget.dashboard.findElementIndexById(element.destElementId) < 0
|
||||
);
|
||||
widget.dashboard.findElementIndexById(element.destElementId) < 0);
|
||||
return Stack( children: [
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
@@ -801,7 +798,7 @@ class ChartWidgetState extends State<ChartWidget> {
|
||||
),
|
||||
// Draw elements
|
||||
for (int i = 0; i < widget.dashboard.elements.length; i++)
|
||||
ElementWidget(
|
||||
ElementWidget<T>(
|
||||
key: UniqueKey(),
|
||||
dashboard: widget.dashboard,
|
||||
element: widget.dashboard.elements.elementAt(i),
|
||||
@@ -904,10 +901,6 @@ class ChartWidgetState extends State<ChartWidget> {
|
||||
dashboard: widget.dashboard,
|
||||
width: MediaQuery.of(context).size.width)
|
||||
),
|
||||
widget.dashboard.isInfo ? Positioned(top: 50, right: 0, child:
|
||||
FlowChartSelectedMenu(key: widget.dashboard.selectedMenuKey, chart: this,
|
||||
dashboard: widget.dashboard, height: MediaQuery.of(context).size.height - 100)
|
||||
) : Container()
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
107
library/flutter_flow_chart/lib/src/flow_chart_left_menu.dart
Normal file
107
library/flutter_flow_chart/lib/src/flow_chart_left_menu.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
|
||||
class FlowChartLeftMenu<T extends FlowData> extends StatefulWidget {
|
||||
Dashboard dashboard;
|
||||
List<String> categories;
|
||||
double height;
|
||||
double itemWidth;
|
||||
double innerMenuWidth;
|
||||
Widget Function()? menuExtension;
|
||||
TextEditingController ctrl = TextEditingController();
|
||||
final List<T> Function(String cat) draggableItemBuilder;
|
||||
|
||||
List<Draggable<T>> Function(List<T> items) getDraggable;
|
||||
|
||||
FlowChartLeftMenu ({ super.key, required this.categories, required this.dashboard,
|
||||
this.height = 100,
|
||||
this.innerMenuWidth = 100,
|
||||
this.itemWidth = 100,
|
||||
this.menuExtension,
|
||||
required this.getDraggable,
|
||||
required this.draggableItemBuilder
|
||||
});
|
||||
@override FlowChartLeftMenuState createState() => FlowChartLeftMenuState();
|
||||
}
|
||||
class FlowChartLeftMenuState<T extends FlowData> extends State<FlowChartLeftMenu> {
|
||||
@override Widget build(BuildContext context) {
|
||||
List<Widget> menuItems = [];
|
||||
for (var cat in widget.categories) {
|
||||
if (widget.draggableItemBuilder(cat).isEmpty) continue;
|
||||
var items = widget.draggableItemBuilder(cat).where(
|
||||
(element) => element.getName().toLowerCase().contains(widget.ctrl.value.text.toLowerCase())).toSet().toList();
|
||||
menuItems.add(
|
||||
Container( width: widget.dashboard.isMenu ? widget.innerMenuWidth : 0, margin: const EdgeInsets.only(bottom: 0.3),
|
||||
decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey, width: .5))),
|
||||
child: Stack( children: [
|
||||
widget.dashboard.isMenu && widget.innerMenuWidth < 200 ? Wrap( alignment: WrapAlignment.start,
|
||||
children: widget.getDraggable(items))
|
||||
: ExpansionTile(
|
||||
shape: const ContinuousRectangleBorder(side: BorderSide(color: Colors.transparent)),
|
||||
initiallyExpanded: true,
|
||||
title: SizedBox(
|
||||
child : Row( children: [
|
||||
Padding(padding: const EdgeInsets.only(right: 10),
|
||||
child: Icon(cat.toUpperCase().contains("DATA") ? Icons.grid_on : Icons.bookmark, color: Colors.grey)),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text(cat.toUpperCase(), overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.black, fontSize: 11, fontWeight: FontWeight.w500))))
|
||||
])
|
||||
),
|
||||
iconColor: Colors.white,
|
||||
collapsedIconColor: Colors.white,
|
||||
children: [
|
||||
Container( width: widget.dashboard.isMenu ? widget.innerMenuWidth : 0, color: Colors.white,
|
||||
child : Wrap( alignment: WrapAlignment.center,
|
||||
children: widget.getDraggable(items))
|
||||
)],
|
||||
)
|
||||
]
|
||||
))
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
height: widget.height - 50,
|
||||
color: Colors.grey.shade300,
|
||||
child: Stack( children: [
|
||||
Container(
|
||||
width: widget.innerMenuWidth,
|
||||
height: 50,
|
||||
decoration: const BoxDecoration(border: Border(left: BorderSide(color: Colors.white))),
|
||||
child: TextFormField(
|
||||
style: const TextStyle(color: Colors.black, fontSize: 15),
|
||||
cursorColor: const Color.fromARGB(38, 166, 154, 1),
|
||||
controller: widget.ctrl,
|
||||
onChanged: (value) { setState(() { }); },
|
||||
decoration: InputDecoration(
|
||||
hintText: "search item...",
|
||||
fillColor: Colors.white,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.only(left: 30, right: 30, top: 5, bottom: 5),
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400
|
||||
),
|
||||
border: InputBorder.none
|
||||
)
|
||||
)
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 50),
|
||||
height: widget.height - 150,
|
||||
constraints: BoxConstraints(minWidth: widget.itemWidth),
|
||||
width: widget.dashboard.isMenu ? widget.innerMenuWidth : 0,
|
||||
color: Colors.grey.shade300,
|
||||
child: SingleChildScrollView( child: Column( children: [
|
||||
...menuItems
|
||||
]) )
|
||||
),
|
||||
Positioned( bottom: 0, height: 50,
|
||||
child: widget.menuExtension != null ? widget.menuExtension!() : Container(),),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ class FlowChartMenuState extends State<FlowChartMenu> {
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
widget.dashboard.defaultName = "graph_${DateTime.now().toString().replaceAll(" ", "_").substring(0, DateTime.now().toString().length - 7)}";
|
||||
widget.dashboard.isOpened = true;
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context, builder: (context) {
|
||||
|
||||
@@ -6,16 +6,78 @@ import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:number_text_input_formatter/number_text_input_formatter.dart';
|
||||
|
||||
class FlowChartSelectedMenu extends StatefulWidget {
|
||||
ChartWidgetState chart;
|
||||
Dashboard dashboard;
|
||||
double height = 100;
|
||||
|
||||
FlowChartSelectedMenu ({ super.key, required this.chart, required this.dashboard, this.height = 100 });
|
||||
bool isDashboardInfo = true;
|
||||
FlowChartSelectedMenu ({ super.key, required this.dashboard, this.height = 100 });
|
||||
@override FlowChartSelectedMenuState createState() => FlowChartSelectedMenuState();
|
||||
}
|
||||
class FlowChartSelectedMenuState extends State<FlowChartSelectedMenu> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Container( // SHORTCUT
|
||||
Widget? w;
|
||||
if (widget.isDashboardInfo && widget.dashboard.elementSelected.length == 1) {
|
||||
w = Container(
|
||||
width: 200,
|
||||
height: widget.height,
|
||||
color: Colors.grey.shade300,
|
||||
child: Column( children: [ ...widget.dashboard.infoItemWidget != null ?
|
||||
widget.dashboard.infoItemWidget!(widget.dashboard.elementSelected.first.element)
|
||||
: [],
|
||||
widget.dashboard.arrowsSelected.isNotEmpty || widget.dashboard.elementSelected.isNotEmpty ? Container(
|
||||
width: 200,
|
||||
margin: EdgeInsets.only(top: 15),
|
||||
decoration: BoxDecoration(border: Border(
|
||||
top: BorderSide(color: Colors.grey, width: 1),
|
||||
bottom: BorderSide(color: Colors.grey, width: 1))),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Tooltip( message: "remove",
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
widget.dashboard.arrows.removeWhere((element) {
|
||||
if (element.isSelected && element.elementIndex != null && element.connIndex != null) {
|
||||
widget.dashboard.elements[element.elementIndex!].next.removeAt(element.connIndex!);
|
||||
}
|
||||
return element.isSelected;
|
||||
});
|
||||
widget.dashboard.elements.removeWhere((element) => element.isSelected);
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
widget.dashboard.chartKey.currentState?.setState(() { });
|
||||
});
|
||||
}, child: Container( margin: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black, width: 1)),
|
||||
width: 140, height: 30,
|
||||
child: Icon(Icons.delete_outline, color: Colors.black),
|
||||
))
|
||||
),
|
||||
Tooltip( message: "copy",
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
for (var sel in widget.dashboard.elementSelected) {
|
||||
widget.dashboard.elements.add(FlowElement.fromMap(widget.dashboard, sel.toMap()));
|
||||
widget.dashboard.elements.last.position += Offset(50, 50);
|
||||
}
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
widget.dashboard.chartKey.currentState?.setState(() { });
|
||||
});
|
||||
}, child: Container( margin: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black, width: 1)),
|
||||
width: 140, height: 30,
|
||||
child: Icon(Icons.copy, color: Colors.black),
|
||||
))
|
||||
),
|
||||
])
|
||||
) : Container()
|
||||
])
|
||||
);
|
||||
} else if (widget.isDashboardInfo && widget.dashboard.infoWidget != null) {
|
||||
w = Container(
|
||||
width: 200,
|
||||
height: widget.height,
|
||||
color: Colors.grey.shade300,
|
||||
child: Column( children: widget.dashboard.infoWidget != null ? widget.dashboard.infoWidget!() : [])
|
||||
);
|
||||
} else {
|
||||
w = Container( // SHORTCUT
|
||||
width: 200,
|
||||
height: widget.height,
|
||||
color: Colors.grey.shade300,
|
||||
@@ -292,6 +354,9 @@ class FlowChartSelectedMenuState extends State<FlowChartSelectedMenu> {
|
||||
)))
|
||||
]),
|
||||
])),
|
||||
widget.dashboard.elementSelected.isNotEmpty && widget.dashboard.elementSelected.length == 1 ? Container(
|
||||
// TODO : TEST OMG
|
||||
) : Container(),
|
||||
widget.dashboard.elementSelected.isNotEmpty ? Container() : Container( padding: EdgeInsets.only(left: 10, right: 10, bottom: 20, top: 15),
|
||||
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey, width: 1))),
|
||||
child: Column( children: [
|
||||
@@ -477,7 +542,7 @@ class FlowChartSelectedMenuState extends State<FlowChartSelectedMenu> {
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
for (var sel in widget.dashboard.elementSelected) {
|
||||
widget.dashboard.elements.add(FlowElement.fromMap(sel.toMap()));
|
||||
widget.dashboard.elements.add(FlowElement.fromMap(widget.dashboard, sel.toMap()));
|
||||
widget.dashboard.elements.last.position += Offset(50, 50);
|
||||
}
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
@@ -494,6 +559,35 @@ class FlowChartSelectedMenuState extends State<FlowChartSelectedMenu> {
|
||||
])))
|
||||
])
|
||||
);
|
||||
}
|
||||
return Column( children: [
|
||||
Container( // SHORTCUT
|
||||
width: 200,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(color: Colors.grey.shade300, border: Border(bottom: BorderSide(color: Colors.grey, width: 1))),
|
||||
child: Row( children: [
|
||||
Tooltip(
|
||||
message: "dashboard information",
|
||||
child: InkWell( onTap: () => setState(() {widget.isDashboardInfo = true; }),
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
child: Container( alignment: Alignment.center,
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
color: widget.isDashboardInfo ? Colors.grey : Colors.grey.shade300,
|
||||
width: 100, child: Icon(Icons.info, color: Colors.white))
|
||||
)
|
||||
),
|
||||
Tooltip(
|
||||
message: "element style",
|
||||
child: InkWell( onTap: () => setState(() {widget.isDashboardInfo = false; }),
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
child: Container( alignment: Alignment.center,
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
color: !widget.isDashboardInfo ? Colors.grey : Colors.grey.shade300,
|
||||
width: 100, child: Icon(Icons.format_paint, color: Colors.white)),
|
||||
))
|
||||
])),
|
||||
w
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:flutter_flow_chart/src/ui/segment_handler.dart';
|
||||
|
||||
@@ -349,6 +351,7 @@ class DrawArrowState extends State<DrawArrow> {
|
||||
if ( widget.flow.widget.dashboard.arrows.where(
|
||||
(element) => element.fromID == "${widget.srcElement.id}_${widget.index}").isEmpty) {
|
||||
widget.flow.widget.dashboard.arrows.add(painter);
|
||||
widget.flow.widget.dashboard.save!(widget.flow.widget.dashboard.id);
|
||||
} else {
|
||||
var i = widget.flow.widget.dashboard.arrows.indexWhere(
|
||||
(element) => element.fromID == "${widget.srcElement.id}_${widget.index}");
|
||||
@@ -416,24 +419,24 @@ class ArrowInfoWidgetState extends State<ArrowInfoWidget> {
|
||||
/// [ArrowParams.startArrowPosition] and
|
||||
/// [ArrowParams.endArrowPosition] alignment.
|
||||
class ArrowPainter extends CustomPainter {
|
||||
final String fromID;
|
||||
final String toID;
|
||||
String fromID;
|
||||
String toID;
|
||||
bool isSelected = false;
|
||||
///
|
||||
ArrowPainter({
|
||||
this.elementIndex,
|
||||
this.connIndex,
|
||||
required this.toID,
|
||||
required this.fromID,
|
||||
this.toID = "",
|
||||
this.fromID = "",
|
||||
this.isSelected = false,
|
||||
required this.params,
|
||||
required this.from,
|
||||
required this.to,
|
||||
this.from = Offset.zero,
|
||||
this.to = Offset.zero,
|
||||
List<Pivot>? pivots,
|
||||
}) : pivots = pivots ?? [];
|
||||
|
||||
///
|
||||
final ArrowParams params;
|
||||
ArrowParams params;
|
||||
///
|
||||
Offset from;
|
||||
int? elementIndex;
|
||||
@@ -457,6 +460,15 @@ class ArrowPainter extends CustomPainter {
|
||||
final arrowSize = 15;
|
||||
final arrowAngle= 25 * math.pi / 180;
|
||||
|
||||
ArrowPainter deserialize(Map<String, dynamic> map) {
|
||||
params = ArrowParams.fromMap(map['params']);
|
||||
fromID = map['from']['id'];
|
||||
toID = map['to']['id'];
|
||||
from = Offset(map['from']['x'], map['from']['y']);
|
||||
to = Offset(map['to']['x'], map['to']['y']);
|
||||
return this;
|
||||
}
|
||||
|
||||
Map<String, dynamic> serialize() {
|
||||
Map<String, dynamic> graphElement = {};
|
||||
graphElement['from'] = { "id" : fromID.split("_")[0], "x" : from.dx, "y" : from.dy };
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:flutter_flow_chart/src/ui/resize_widget.dart';
|
||||
import 'package:flutter_flow_chart/src/objects/any_widget.dart';
|
||||
|
||||
/// Widget that use [element] properties to display it on the dashboard scene
|
||||
class ElementWidget extends StatefulWidget {
|
||||
class ElementWidget<T extends FlowData> extends StatefulWidget {
|
||||
///
|
||||
ElementWidget({
|
||||
required this.dashboard,
|
||||
@@ -84,10 +84,10 @@ class ElementWidget extends StatefulWidget {
|
||||
)? onHandlerSecondaryLongTapped;
|
||||
|
||||
@override
|
||||
State<ElementWidget> createState() => ElementWidgetState();
|
||||
State<ElementWidget> createState() => ElementWidgetState<T>();
|
||||
}
|
||||
|
||||
class ElementWidgetState extends State<ElementWidget> {
|
||||
class ElementWidgetState<T extends FlowData> extends State<ElementWidget> {
|
||||
// local widget touch position when start dragging
|
||||
Offset delta = Offset.zero;
|
||||
|
||||
@@ -219,6 +219,7 @@ class ElementWidgetState extends State<ElementWidget> {
|
||||
sel.changePosition(sel.position + diff);
|
||||
}
|
||||
}
|
||||
widget.dashboard.save!(widget.dashboard.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -228,7 +229,8 @@ class ElementWidgetState extends State<ElementWidget> {
|
||||
element: widget.element,
|
||||
dashboard: widget.dashboard,
|
||||
handlerColor: widget.isHovered ? Color.fromRGBO(38, 166, 154, 1) : Colors.transparent,
|
||||
child: w );
|
||||
child: w
|
||||
);
|
||||
return Transform.translate(
|
||||
offset: widget.element.position,
|
||||
transformHitTests: true,
|
||||
@@ -245,7 +247,9 @@ class ElementWidgetState extends State<ElementWidget> {
|
||||
widget.dashboard.removeElement(widget.element);
|
||||
}, icon: Icon(Icons.delete_outline)),
|
||||
IconButton(tooltip: "copy element", onPressed: () {
|
||||
FlowElement newElement = FlowElement(
|
||||
FlowElement<T> newElement = FlowElement<T>(
|
||||
element: widget.element.element as T?,
|
||||
dashboard: widget.dashboard,
|
||||
kind: widget.element.kind,
|
||||
position: widget.element.position + Offset(widget.element.size.width, widget.element.size.height),
|
||||
size: widget.element.size,
|
||||
|
||||
Reference in New Issue
Block a user