From e4e3da747092f8d6b927e121b8c310868a217c77 Mon Sep 17 00:00:00 2001 From: DanPeled <98838880+DanPeled@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:41:15 +0300 Subject: [PATCH 1/8] added copy & paste --- lib/pages/dashboard_page.dart | 130 +++++++++++++++++++++----- lib/widgets/editable_tab_bar.dart | 42 +++++++++ lib/widgets/nt_widgets/nt_widget.dart | 10 ++ lib/widgets/tab_grid.dart | 124 ++++++++++++++++++++---- 4 files changed, 269 insertions(+), 37 deletions(-) diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index c1a37977..d9297101 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -52,6 +53,7 @@ class DashboardPage extends StatefulWidget { } class _DashboardPageState extends State with WindowListener { + Timer? _timer; late final SharedPreferences _preferences; late final UpdateChecker _updateChecker; late final RobotNotificationsListener _robotNotificationListener; @@ -68,10 +70,12 @@ class _DashboardPageState extends State with WindowListener { bool _addWidgetDialogVisible = false; + String _prevRobotState = "Unknown"; + @override void initState() { super.initState(); - + _startTimer(); _preferences = widget.preferences; _updateChecker = UpdateChecker(currentVersion: widget.version); @@ -847,7 +851,7 @@ class _DashboardPageState extends State with WindowListener { Container( constraints: const BoxConstraints(maxWidth: 353), child: const Text( - 'Elastic was created by Team 353, the POBots in the summer of 2023. The motivation was to provide teams an alternative to WPILib\'s Shuffleboard dashboard.\n', + 'Elastic was created by Team 353 (Modified by 9738), the POBots in the summer of 2023. The motivation was to provide teams an alternative to WPILib\'s Shuffleboard dashboard.\n', ), ), Container( @@ -1317,8 +1321,13 @@ class _DashboardPageState extends State with WindowListener { (!Settings.layoutLocked) ? () => _importLayout() : null, shortcut: const SingleActivator(LogicalKeyboardKey.keyO, control: true), - child: const Text( - 'Open Layout', + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.folder_open_outlined), + SizedBox(width: 8), + Text('Open Layout'), + ], ), ), // Save @@ -1329,22 +1338,32 @@ class _DashboardPageState extends State with WindowListener { }, shortcut: const SingleActivator(LogicalKeyboardKey.keyS, control: true), - child: const Text( - 'Save', + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.save_outlined), + SizedBox(width: 8), + Text('Save'), + ], ), ), + // Export layout MenuItemButton( - style: menuButtonStyle, - onPressed: () { - _exportLayout(); - }, - shortcut: const SingleActivator(LogicalKeyboardKey.keyS, - shift: true, control: true), - child: const Text( - 'Save As', - ), - ), + style: menuButtonStyle, + onPressed: () { + _exportLayout(); + }, + shortcut: const SingleActivator(LogicalKeyboardKey.keyS, + shift: true, control: true), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.save_as_outlined), + SizedBox(width: 8), + Text('Save As'), + ], + )), ], child: const Text( 'File', @@ -1399,8 +1418,13 @@ class _DashboardPageState extends State with WindowListener { onPressed: () { _displayAboutDialog(context); }, - child: const Text( - 'About', + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.info_outline), + SizedBox(width: 8), + Text('About'), + ], ), ), // Check for Updates @@ -1409,8 +1433,13 @@ class _DashboardPageState extends State with WindowListener { onPressed: () { _checkForUpdates(); }, - child: const Text( - 'Check for Updates', + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.update_outlined), + SizedBox(width: 8), + Text('Check for updates'), + ], ), ), ], @@ -1522,6 +1551,19 @@ class _DashboardPageState extends State with WindowListener { onTabChanged: (index) { setState(() => _currentTabIndex = index); }, + onTabDuplicateTab: (index, tab) { + setState(() { + _tabData.add(tab); + _grids.add(TabGrid( + key: GlobalKey(), + onAddWidgetPressed: _displayAddWidgetDialog, + )); + _grids.last.addAllWidget(TabGrid.fromJson( + jsonData: _grids[index].toJson(), + onAddWidgetPressed: () {}) + .getAllWidget()); + }); + }, tabData: _tabData, tabViews: _grids, ), @@ -1603,6 +1645,53 @@ class _DashboardPageState extends State with WindowListener { ), ); } + + void _startTimer() { + _timer = Timer.periodic(const Duration(milliseconds: 500), (Timer timer) { + switchTabsAuto(); + }); + } + + void switchTabsAuto() { + int controlData = tryCast( + ntConnection.getLastAnnouncedValue('/FMSInfo/FMSControlData')) ?? + 32; + + String state = getRobotState(controlData); + + if (_prevRobotState != state) { + for (int i = 0; i < _tabData.length; i++) { + if (state == _tabData[i].name) { + setState(() => _currentTabIndex = i); + } + } + _prevRobotState = state; + } + } + + String getRobotState(int controlData) { + const int enabledFlag = 0x01; + const int autoFlag = 0x02; + const int testFlag = 0x04; + + String robotControlState = 'Disabled'; + + if (_flagMatches(controlData, enabledFlag)) { + if (_flagMatches(controlData, testFlag)) { + robotControlState = 'Test'; + } else if (_flagMatches(controlData, autoFlag)) { + robotControlState = 'Autonomous'; + } else { + robotControlState = 'Teleoperated'; + } + } + + return robotControlState; + } + + bool _flagMatches(int word, int flag) { + return (word & flag) != 0; + } } class _AddWidgetDialog extends StatefulWidget { @@ -1641,7 +1730,6 @@ class _AddWidgetDialog extends StatefulWidget { class _AddWidgetDialogState extends State<_AddWidgetDialog> { bool _hideMetadata = true; - @override Widget build(BuildContext context) { return Visibility( diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index c7705a98..7c15a67b 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -25,6 +25,7 @@ class EditableTabBar extends StatelessWidget { final Function() onTabMoveRight; final Function(int index, TabData newData) onTabRename; final Function(int index) onTabChanged; + final Function(int index, TabData newData) onTabDuplicateTab; final int currentIndex; @@ -39,6 +40,7 @@ class EditableTabBar extends StatelessWidget { required this.onTabMoveRight, required this.onTabRename, required this.onTabChanged, + required this.onTabDuplicateTab, }); void renameTab(BuildContext context, int index) { @@ -74,6 +76,41 @@ class EditableTabBar extends StatelessWidget { ); } + void duplicateTab(BuildContext context, int index) { + String tabName = 'Tab ${tabData.length + 1}'; + TabData data = TabData(name: tabName); + + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('name for duplicateTab Tab'), + content: Container( + constraints: const BoxConstraints( + maxWidth: 200, + ), + child: DialogTextInput( + onSubmit: (value) { + data.name = value; + }, + initialText: data.name, + label: 'Name', + formatter: LengthLimitingTextInputFormatter(50), + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Save'), + ), + ], + ); + }, + ).whenComplete(() => (onTabDuplicateTab.call(index, data),)); + } + void createTab() { String tabName = 'Tab ${tabData.length + 1}'; TabData data = TabData(name: tabName); @@ -142,6 +179,11 @@ class EditableTabBar extends StatelessWidget { icon: Icons.drive_file_rename_outline_outlined, onSelected: () => renameTab(context, index), ), + MenuItem( + label: 'Duplicate', + icon: Icons.control_point_duplicate_sharp, + onSelected: () => duplicateTab(context, index), + ), MenuItem( label: 'Close', icon: Icons.close, diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index 9a81563d..fbf80177 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -18,6 +18,7 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/text_display.d import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_button.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/voltage_view.dart'; +import 'package:flutter/services.dart'; class NTWidgetModel extends ChangeNotifier { String _typeOverride = 'NTWidget'; @@ -89,6 +90,10 @@ class NTWidgetModel extends ChangeNotifier { } List getAvailableDisplayTypes() { + if (type == 'Field' || type == 'Field Aoltra') { + return ['Field', 'Field Aoltra']; + } + if (type == 'ComboBox Chooser' || type == 'Split Button Chooser') { return ['ComboBox Chooser', 'Split Button Chooser']; } @@ -242,4 +247,9 @@ class NTWidgetModel extends ChangeNotifier { abstract class NTWidget extends StatelessWidget { const NTWidget({super.key}); + + void onTap() {} + void onDoubleTap() {} + void onHover(PointerHoverEvent event) {} + void onSecondaryTap() {} } diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 905fef69..99a182d5 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -1,5 +1,5 @@ import 'dart:math'; - +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; @@ -27,6 +27,7 @@ class TabGridModel extends ChangeNotifier { } class TabGrid extends StatelessWidget { + static Map? _copyJsonData; final List _widgetModels = []; MapEntry? _containerDraggingIn; @@ -460,6 +461,7 @@ class TabGrid extends StatelessWidget { widget.draggingRect.height, ), ); + _containerDraggingIn = MapEntry(widget, globalPosition); refresh(); } @@ -516,7 +518,6 @@ class TabGrid extends StatelessWidget { _containerDraggingIn = null; widget.disposeModel(); - refresh(); } @@ -542,6 +543,17 @@ class TabGrid extends StatelessWidget { _widgetModels.add(widget); } + List getAllWidget() { + return _widgetModels; + } + + void addAllWidget(List widgets) { + for (var element in widgets) { + _widgetModels.add(element); + } + refresh(); + } + void addWidgetFromTabJson(Map widgetData) { Rect newWidgetLocation = Rect.fromLTWH( tryCast(widgetData['x']) ?? 0.0, @@ -648,6 +660,10 @@ class TabGrid extends StatelessWidget { ); } + void copyWidget(WidgetContainerModel widget) { + _copyJsonData = widget.toJson(); + } + void lockLayout() { for (WidgetContainerModel container in _widgetModels) { container.setDraggable(false); @@ -782,8 +798,16 @@ class TabGrid extends StatelessWidget { dashboardWidgets.add( GestureDetector( - onTap: () {}, + onTap: () { + var widget = getWidgetFromContainer(container); + widget?.onTap(); + }, + onDoubleTap: () { + getWidgetFromContainer(container)?.onDoubleTap(); + }, onSecondaryTapUp: (details) { + getWidgetFromContainer(container)?.onSecondaryTap(); + if (Settings.layoutLocked) { return; } @@ -807,6 +831,12 @@ class TabGrid extends StatelessWidget { onSelected: () { removeWidget(container); }), + MenuItem( + label: 'Copy', + icon: Icons.copy_outlined, + onSelected: () { + copyWidget(container); + }) ]; ContextMenu contextMenu = ContextMenu( @@ -830,7 +860,12 @@ class TabGrid extends StatelessWidget { }, ); }, - child: getWidgetFromModel(container), + child: MouseRegion( + onHover: (event) { + getWidgetFromContainer(container)?.onHover(event); + }, + hitTestBehavior: HitTestBehavior.deferToChild, + child: getWidgetFromModel(container)), ), ); } @@ -898,22 +933,37 @@ class TabGrid extends StatelessWidget { if (Settings.layoutLocked) { return; } + + List contextMenuEntries = [ + MenuItem( + label: 'Add Widget', + icon: Icons.add, + onSelected: () => onAddWidgetPressed.call(), + ), + MenuItem( + label: 'Clear Layout', + icon: Icons.clear, + onSelected: () => clearWidgets(context), + ), + ]; + + if (_copyJsonData != null) { + contextMenuEntries.add( + MenuItem( + label: 'Paste', + icon: Icons.paste_outlined, + onSelected: () { + pasteWidget(_copyJsonData, details.globalPosition); + }, + ), + ); + } + ContextMenu contextMenu = ContextMenu( position: details.globalPosition, borderRadius: BorderRadius.circular(5.0), padding: const EdgeInsets.all(4.0), - entries: [ - MenuItem( - label: 'Add Widget', - icon: Icons.add, - onSelected: () => onAddWidgetPressed.call(), - ), - MenuItem( - label: 'Clear Layout', - icon: Icons.clear, - onSelected: () => clearWidgets(context), - ), - ], + entries: contextMenuEntries, ); showContextMenu( @@ -939,4 +989,46 @@ class TabGrid extends StatelessWidget { ), ); } + + void pasteWidget(Map? widgetJson, Offset globalPosition) { + if (widgetJson == null) return; + + widgetJson['x'] = DraggableWidgetContainer.snapToGrid( + getLocalPosition(globalPosition).dx); + widgetJson['y'] = DraggableWidgetContainer.snapToGrid( + getLocalPosition(globalPosition).dy); + + WidgetContainerModel createdWidget = createWidgetFromJson(widgetJson); + + _widgetModels.add(createdWidget); + refresh(); + } + + NTWidget? getWidgetFromNTContainer(NTWidgetContainerModel? container) { + return container?.child; + } + + NTWidget? getWidgetFromContainer(WidgetContainerModel? container) { + NTWidgetContainerModel? w = tryCast(container); + return w?.child; + } + + WidgetContainerModel createWidgetFromJson(Map json) { + String type = json['type']; + if (json['type'] == 'List Layout') { + switch (type) { + case 'List Layout': + return ListLayoutModel.fromJson( + jsonData: json, tabGrid: this, onDragCancel: null); + default: + throw ArgumentError('Unknown type: $type'); + } + } else { + return NTWidgetContainerModel.fromJson( + enabled: ntConnection.isNT4Connected, + jsonData: json, + onJsonLoadingWarning: null, + ); + } + } } From dfce9c39529f7066d426bbc46121db2b5a552603 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:18:43 -0400 Subject: [PATCH 2/8] Moved copy above remove --- lib/widgets/tab_grid.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 99a182d5..a1a8692c 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -825,18 +825,18 @@ class TabGrid extends StatelessWidget { }, ), ...container.getContextMenuItems(), + MenuItem( + label: 'Copy', + icon: Icons.copy_outlined, + onSelected: () { + copyWidget(container); + }), MenuItem( label: 'Remove', icon: Icons.delete_outlined, onSelected: () { removeWidget(container); }), - MenuItem( - label: 'Copy', - icon: Icons.copy_outlined, - onSelected: () { - copyWidget(container); - }) ]; ContextMenu contextMenu = ContextMenu( From 164991f72d1db5c1b78f8c3efa3671724c628a38 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:34:24 -0400 Subject: [PATCH 3/8] Cleaned up pasting logic --- lib/widgets/nt_widgets/nt_widget.dart | 6 --- lib/widgets/tab_grid.dart | 66 +++++++++++---------------- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index fbf80177..6f19ef7a 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -18,7 +18,6 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/text_display.d import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_button.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/voltage_view.dart'; -import 'package:flutter/services.dart'; class NTWidgetModel extends ChangeNotifier { String _typeOverride = 'NTWidget'; @@ -247,9 +246,4 @@ class NTWidgetModel extends ChangeNotifier { abstract class NTWidget extends StatelessWidget { const NTWidget({super.key}); - - void onTap() {} - void onDoubleTap() {} - void onHover(PointerHoverEvent event) {} - void onSecondaryTap() {} } diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index a1a8692c..b4f8faf5 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -1,5 +1,5 @@ import 'dart:math'; -import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; + import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; @@ -798,16 +798,7 @@ class TabGrid extends StatelessWidget { dashboardWidgets.add( GestureDetector( - onTap: () { - var widget = getWidgetFromContainer(container); - widget?.onTap(); - }, - onDoubleTap: () { - getWidgetFromContainer(container)?.onDoubleTap(); - }, onSecondaryTapUp: (details) { - getWidgetFromContainer(container)?.onSecondaryTap(); - if (Settings.layoutLocked) { return; } @@ -860,12 +851,7 @@ class TabGrid extends StatelessWidget { }, ); }, - child: MouseRegion( - onHover: (event) { - getWidgetFromContainer(container)?.onHover(event); - }, - hitTestBehavior: HitTestBehavior.deferToChild, - child: getWidgetFromModel(container)), + child: getWidgetFromModel(container), ), ); } @@ -993,41 +979,43 @@ class TabGrid extends StatelessWidget { void pasteWidget(Map? widgetJson, Offset globalPosition) { if (widgetJson == null) return; - widgetJson['x'] = DraggableWidgetContainer.snapToGrid( - getLocalPosition(globalPosition).dx); - widgetJson['y'] = DraggableWidgetContainer.snapToGrid( - getLocalPosition(globalPosition).dy); + Offset localPosition = getLocalPosition(globalPosition); - WidgetContainerModel createdWidget = createWidgetFromJson(widgetJson); + // Put the top left corner of the widget in the square the user pastes it in + double snappedX = + (localPosition.dx ~/ Settings.gridSize) * Settings.gridSize.toDouble(); + double snappedY = + (localPosition.dy ~/ Settings.gridSize) * Settings.gridSize.toDouble(); - _widgetModels.add(createdWidget); - refresh(); - } + widgetJson['x'] = snappedX; + widgetJson['y'] = snappedY; - NTWidget? getWidgetFromNTContainer(NTWidgetContainerModel? container) { - return container?.child; - } + Rect pasteLocation = Rect.fromLTWH( + snappedX, + snappedY, + widgetJson['width'], + widgetJson['height'], + ); + + if (isValidLocation(pasteLocation)) { + WidgetContainerModel copiedWidget = createWidgetFromJson(widgetJson); - NTWidget? getWidgetFromContainer(WidgetContainerModel? container) { - NTWidgetContainerModel? w = tryCast(container); - return w?.child; + _widgetModels.add(copiedWidget); + refresh(); + } } WidgetContainerModel createWidgetFromJson(Map json) { - String type = json['type']; if (json['type'] == 'List Layout') { - switch (type) { - case 'List Layout': - return ListLayoutModel.fromJson( - jsonData: json, tabGrid: this, onDragCancel: null); - default: - throw ArgumentError('Unknown type: $type'); - } + return ListLayoutModel.fromJson( + jsonData: json, + tabGrid: this, + onDragCancel: _layoutContainerOnDragCancel, + ); } else { return NTWidgetContainerModel.fromJson( enabled: ntConnection.isNT4Connected, jsonData: json, - onJsonLoadingWarning: null, ); } } From ce12676c44f7ef4965a730f06d2377092fef7823 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:46:24 -0400 Subject: [PATCH 4/8] Cleaned up and added tests for tab duplication --- lib/pages/dashboard_page.dart | 72 ++++--------------------- lib/widgets/editable_tab_bar.dart | 36 ++----------- lib/widgets/tab_grid.dart | 11 ---- test/pages/dashboard_page_test.dart | 32 +++++++++++ test/widgets/editable_tab_bar_test.dart | 49 +++++++++++------ 5 files changed, 80 insertions(+), 120 deletions(-) diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index d9297101..25efe6f1 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -53,7 +53,6 @@ class DashboardPage extends StatefulWidget { } class _DashboardPageState extends State with WindowListener { - Timer? _timer; late final SharedPreferences _preferences; late final UpdateChecker _updateChecker; late final RobotNotificationsListener _robotNotificationListener; @@ -70,12 +69,9 @@ class _DashboardPageState extends State with WindowListener { bool _addWidgetDialogVisible = false; - String _prevRobotState = "Unknown"; - @override void initState() { super.initState(); - _startTimer(); _preferences = widget.preferences; _updateChecker = UpdateChecker(currentVersion: widget.version); @@ -1551,17 +1547,18 @@ class _DashboardPageState extends State with WindowListener { onTabChanged: (index) { setState(() => _currentTabIndex = index); }, - onTabDuplicateTab: (index, tab) { + onTabDuplicate: (index, tab) { setState(() { - _tabData.add(tab); - _grids.add(TabGrid( - key: GlobalKey(), - onAddWidgetPressed: _displayAddWidgetDialog, - )); - _grids.last.addAllWidget(TabGrid.fromJson( - jsonData: _grids[index].toJson(), - onAddWidgetPressed: () {}) - .getAllWidget()); + _tabData.insert(index + 1, tab); + Map tabJson = _grids[index].toJson(); + _grids.insert( + index + 1, + TabGrid.fromJson( + key: GlobalKey(), + jsonData: tabJson, + onAddWidgetPressed: _displayAddWidgetDialog, + onJsonLoadingWarning: _showJsonLoadingWarning, + )); }); }, tabData: _tabData, @@ -1645,53 +1642,6 @@ class _DashboardPageState extends State with WindowListener { ), ); } - - void _startTimer() { - _timer = Timer.periodic(const Duration(milliseconds: 500), (Timer timer) { - switchTabsAuto(); - }); - } - - void switchTabsAuto() { - int controlData = tryCast( - ntConnection.getLastAnnouncedValue('/FMSInfo/FMSControlData')) ?? - 32; - - String state = getRobotState(controlData); - - if (_prevRobotState != state) { - for (int i = 0; i < _tabData.length; i++) { - if (state == _tabData[i].name) { - setState(() => _currentTabIndex = i); - } - } - _prevRobotState = state; - } - } - - String getRobotState(int controlData) { - const int enabledFlag = 0x01; - const int autoFlag = 0x02; - const int testFlag = 0x04; - - String robotControlState = 'Disabled'; - - if (_flagMatches(controlData, enabledFlag)) { - if (_flagMatches(controlData, testFlag)) { - robotControlState = 'Test'; - } else if (_flagMatches(controlData, autoFlag)) { - robotControlState = 'Autonomous'; - } else { - robotControlState = 'Teleoperated'; - } - } - - return robotControlState; - } - - bool _flagMatches(int word, int flag) { - return (word & flag) != 0; - } } class _AddWidgetDialog extends StatefulWidget { diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index 7c15a67b..61df2898 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -25,7 +25,7 @@ class EditableTabBar extends StatelessWidget { final Function() onTabMoveRight; final Function(int index, TabData newData) onTabRename; final Function(int index) onTabChanged; - final Function(int index, TabData newData) onTabDuplicateTab; + final Function(int index, TabData newData) onTabDuplicate; final int currentIndex; @@ -40,7 +40,7 @@ class EditableTabBar extends StatelessWidget { required this.onTabMoveRight, required this.onTabRename, required this.onTabChanged, - required this.onTabDuplicateTab, + required this.onTabDuplicate, }); void renameTab(BuildContext context, int index) { @@ -77,38 +77,10 @@ class EditableTabBar extends StatelessWidget { } void duplicateTab(BuildContext context, int index) { - String tabName = 'Tab ${tabData.length + 1}'; + String tabName = '${tabData[index].name} (Copy)'; TabData data = TabData(name: tabName); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('name for duplicateTab Tab'), - content: Container( - constraints: const BoxConstraints( - maxWidth: 200, - ), - child: DialogTextInput( - onSubmit: (value) { - data.name = value; - }, - initialText: data.name, - label: 'Name', - formatter: LengthLimitingTextInputFormatter(50), - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Save'), - ), - ], - ); - }, - ).whenComplete(() => (onTabDuplicateTab.call(index, data),)); + onTabDuplicate.call(index, data); } void createTab() { diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index b4f8faf5..95821974 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -543,17 +543,6 @@ class TabGrid extends StatelessWidget { _widgetModels.add(widget); } - List getAllWidget() { - return _widgetModels; - } - - void addAllWidget(List widgets) { - for (var element in widgets) { - _widgetModels.add(element); - } - refresh(); - } - void addWidgetFromTabJson(Map widgetData) { Rect newWidgetLocation = Rect.fromLTWH( tryCast(widgetData['x']) ?? 0.0, diff --git a/test/pages/dashboard_page_test.dart b/test/pages/dashboard_page_test.dart index 2079816c..77e6da29 100644 --- a/test/pages/dashboard_page_test.dart +++ b/test/pages/dashboard_page_test.dart @@ -720,6 +720,38 @@ void main() { findsOneWidget); }); + testWidgets('Duplicating tab', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + setupMockOfflineNT4(); + + await widgetTester.pumpWidget( + MaterialApp( + home: DashboardPage( + preferences: preferences, + version: '0.0.0.0', + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + final teleopTab = find.widgetWithText(AnimatedContainer, 'Teleoperated'); + + expect(teleopTab, findsOneWidget); + + await widgetTester.tap(teleopTab, buttons: kSecondaryButton); + await widgetTester.pumpAndSettle(); + + final duplicateButton = find.text('Duplicate'); + + expect(duplicateButton, findsOneWidget); + + await widgetTester.tap(duplicateButton); + await widgetTester.pumpAndSettle(); + + expect(find.text('Teleoperated (Copy)'), findsOneWidget); + }); + testWidgets('Minimizing window', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; setupMockOfflineNT4(); diff --git a/test/widgets/editable_tab_bar_test.dart b/test/widgets/editable_tab_bar_test.dart index dccb8583..e80379cd 100644 --- a/test/widgets/editable_tab_bar_test.dart +++ b/test/widgets/editable_tab_bar_test.dart @@ -24,6 +24,8 @@ class FakeTabBarFunctions { void onTabRename() {} void onTabChanged() {} + + void onTabDuplicate() {} } void main() { @@ -40,22 +42,22 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( - currentIndex: 0, - tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), - ], - onTabCreate: (tab) {}, - onTabDestroy: (index) {}, - onTabMoveLeft: () {}, - onTabMoveRight: () {}, - onTabRename: (tab, grid) {}, - onTabChanged: (index) {}, - ), + currentIndex: 0, + tabData: [ + TabData(name: 'Teleoperated'), + TabData(name: 'Autonomous'), + ], + tabViews: [ + TabGrid(onAddWidgetPressed: () {}), + TabGrid(onAddWidgetPressed: () {}), + ], + onTabCreate: (tab) {}, + onTabDestroy: (index) {}, + onTabMoveLeft: () {}, + onTabMoveRight: () {}, + onTabRename: (tab, grid) {}, + onTabChanged: (index) {}, + onTabDuplicate: (index, newData) {}), ), ), ); @@ -107,6 +109,9 @@ void main() { onTabChanged: (index) { tabBarFunctions.onTabChanged(); }, + onTabDuplicate: (index, tab) { + tabBarFunctions.onTabDuplicate(); + }, ), ), ), @@ -158,6 +163,9 @@ void main() { onTabChanged: (index) { tabBarFunctions.onTabChanged(); }, + onTabDuplicate: (index, tab) { + tabBarFunctions.onTabDuplicate(); + }, ), ), ), @@ -209,6 +217,9 @@ void main() { onTabChanged: (index) { tabBarFunctions.onTabChanged(); }, + onTabDuplicate: (index, tab) { + tabBarFunctions.onTabDuplicate(); + }, ), ), ), @@ -273,6 +284,9 @@ void main() { onTabChanged: (index) { tabBarFunctions.onTabChanged(); }, + onTabDuplicate: (index, tab) { + tabBarFunctions.onTabDuplicate(); + }, ), ), ), @@ -348,6 +362,9 @@ void main() { onTabChanged: (index) { tabBarFunctions.onTabChanged(); }, + onTabDuplicate: (index, tab) { + tabBarFunctions.onTabDuplicate(); + }, ), ), ), From 946a2050f67fcd5abe336fb73fa30edcf507c063 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:49:17 -0400 Subject: [PATCH 5/8] Removed modified by text --- lib/pages/dashboard_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 25efe6f1..9cc5ab49 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -847,7 +847,7 @@ class _DashboardPageState extends State with WindowListener { Container( constraints: const BoxConstraints(maxWidth: 353), child: const Text( - 'Elastic was created by Team 353 (Modified by 9738), the POBots in the summer of 2023. The motivation was to provide teams an alternative to WPILib\'s Shuffleboard dashboard.\n', + 'Elastic was created by Team 353, the POBots in the summer of 2023. The motivation was to provide teams an alternative to WPILib\'s Shuffleboard dashboard.\n', ), ), Container( From 4c2f05cce55578cb76f8c30a41adc72fbb8911f5 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:51:07 -0400 Subject: [PATCH 6/8] Removed field aoltra --- lib/widgets/nt_widgets/nt_widget.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index 6f19ef7a..9a81563d 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -89,10 +89,6 @@ class NTWidgetModel extends ChangeNotifier { } List getAvailableDisplayTypes() { - if (type == 'Field' || type == 'Field Aoltra') { - return ['Field', 'Field Aoltra']; - } - if (type == 'ComboBox Chooser' || type == 'Split Button Chooser') { return ['ComboBox Chooser', 'Split Button Chooser']; } From 479f9b1db9524ee8be937aa2f010cf1edd3dc2bd Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:04:30 -0400 Subject: [PATCH 7/8] Added test for copy and pasting widget --- test/widgets/tab_grid_test.dart | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/widgets/tab_grid_test.dart b/test/widgets/tab_grid_test.dart index c0c21897..642cf823 100644 --- a/test/widgets/tab_grid_test.dart +++ b/test/widgets/tab_grid_test.dart @@ -228,6 +228,65 @@ void main() async { await widgetTester.pumpAndSettle(); }); + testWidgets('Editing properties', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + widgetTester.view.physicalSize = const Size(1920, 1080); + widgetTester.view.devicePixelRatio = 1.0; + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider( + create: (context) => TabGridModel(), + child: TabGrid.fromJson( + key: GlobalKey(), + jsonData: jsonData['tabs'][0]['grid_layout'], + onAddWidgetPressed: () {}, + ), + ), + ), + ), + ); + + await widgetTester.pump(Duration.zero); + + await widgetTester.ensureVisible(find.text('Test Number')); + + await widgetTester.pumpAndSettle(); + + await widgetTester.tapAt(const Offset(320.0, 64.0), + buttons: kSecondaryButton); + await widgetTester.pumpAndSettle(); + + expect(find.text('Paste'), findsNothing); + + // Dismiss context menu + await widgetTester.tapAt(const Offset(320.0, 64.0)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.text('Test Number'), + buttons: kSecondaryMouseButton); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Test Number'), findsAtLeastNWidgets(2)); + expect(find.text('Copy'), findsOneWidget); + + await widgetTester.tap(find.text('Copy')); + + await widgetTester.pumpAndSettle(); + + await widgetTester.tapAt(const Offset(320.0, 64.0), + buttons: kSecondaryButton); + await widgetTester.pumpAndSettle(); + + expect(find.text('Paste'), findsOneWidget); + await widgetTester.tap(find.text('Paste')); + await widgetTester.pumpAndSettle(); + + expect(find.text('Test Number'), findsNWidgets(2)); + }); + testWidgets('Dragging widgets', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; widgetTester.view.physicalSize = const Size(1920, 1080); From e613564e9f8059aa6946f67f0bd24235f8b7ff54 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:07:17 -0400 Subject: [PATCH 8/8] Simplified local position calculation for pasting --- lib/widgets/tab_grid.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 95821974..91d50236 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -928,7 +928,7 @@ class TabGrid extends StatelessWidget { label: 'Paste', icon: Icons.paste_outlined, onSelected: () { - pasteWidget(_copyJsonData, details.globalPosition); + pasteWidget(_copyJsonData, details.localPosition); }, ), ); @@ -965,11 +965,9 @@ class TabGrid extends StatelessWidget { ); } - void pasteWidget(Map? widgetJson, Offset globalPosition) { + void pasteWidget(Map? widgetJson, Offset localPosition) { if (widgetJson == null) return; - Offset localPosition = getLocalPosition(globalPosition); - // Put the top left corner of the widget in the square the user pastes it in double snappedX = (localPosition.dx ~/ Settings.gridSize) * Settings.gridSize.toDouble();