diff --git a/example/lib/main.dart b/example/lib/main.dart index 04ca148..07b0fbb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:tabbed_view/tabbed_view.dart'; @@ -8,8 +10,7 @@ void main() { class TabbedViewExample extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, home: TabbedViewExamplePage()); + return MaterialApp(debugShowCheckedModeBanner: false, home: TabbedViewExamplePage()); } } @@ -26,23 +27,10 @@ class _TabbedViewExamplePageState extends State { super.initState(); List tabs = []; + tabs.add(TabData(text: 'Tab 1', leading: (context, status) => Icon(Icons.star, size: 16), content: Padding(child: Text('Hello'), padding: EdgeInsets.all(8)))); + tabs.add(TabData(text: 'Tab 2', content: Padding(child: Text('Hello again'), padding: EdgeInsets.all(8)))); tabs.add(TabData( - text: 'Tab 1', - leading: (context, status) => Icon(Icons.star, size: 16), - content: Padding(child: Text('Hello'), padding: EdgeInsets.all(8)))); - tabs.add(TabData( - text: 'Tab 2', - content: - Padding(child: Text('Hello again'), padding: EdgeInsets.all(8)))); - tabs.add(TabData( - closable: false, - text: 'TextField', - content: Padding( - child: TextField( - decoration: InputDecoration( - isDense: true, border: OutlineInputBorder())), - padding: EdgeInsets.all(8)), - keepAlive: true)); + closable: false, text: 'TextField', content: Padding(child: TextField(decoration: InputDecoration(isDense: true, border: OutlineInputBorder())), padding: EdgeInsets.all(8)), keepAlive: true)); _controller = TabbedViewController(tabs); } @@ -50,8 +38,45 @@ class _TabbedViewExamplePageState extends State { @override Widget build(BuildContext context) { TabbedView tabbedView = TabbedView(controller: _controller); - Widget w = - TabbedViewTheme(child: tabbedView, data: TabbedViewThemeData.mobile()); + TabbedViewThemeData themeData = TabbedViewThemeData.classic(); + themeData.tabsArea + ..addButton = TabButton( + icon: IconProvider.data(Icons.add), + onPressed: () { + _controller.addTab(TabData( + text: DateTime.now().toString(), + content: const Center( + child: Text('New Tab'), + ))); + }) + ..dropColor = Colors.red + ..dropOverPainter = _CustomDropOverPainter(); + themeData.menu.maxWidth = 100; + themeData.tab + ..maxWidth = 100 + ..minWidth = 50 + ..decoration = BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.orange, width: 5), + ); + Widget w = TabbedViewTheme(child: tabbedView, data: themeData); return Scaffold(body: Container(child: w, padding: EdgeInsets.all(32))); } } + +class _CustomDropOverPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint() + ..color = Colors.red + ..style = PaintingStyle.fill; + canvas.drawCircle(Offset(0,size.height/2), 4, paint); + canvas.drawRRect(RRect.fromLTRBXY(0, 4, 4, size.height, 2, 2), paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 79158a2..27e0f50 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8fedab6..397f3d3 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { ) { if (_over) { TabbedViewThemeData theme = TabbedViewTheme.of(context); - return CustomPaint( - foregroundPainter: - _CustomPainter(dropColor: theme.tabsArea.dropColor), - child: widget.child); + return CustomPaint(foregroundPainter: theme.tabsArea.dropOverPainter ?? _CustomPainter(dropColor: theme.tabsArea.dropColor), child: widget.child); } return widget.child; }, @@ -72,8 +65,7 @@ class DropTabWidgetState extends State { if (widget.provider.canDrop == null) { _canDrop = true; } else if (data != null) { - _canDrop = - widget.provider.canDrop!(data, widget.provider.controller); + _canDrop = widget.provider.canDrop!(data, widget.provider.controller); } else { _canDrop = false; } @@ -81,9 +73,7 @@ class DropTabWidgetState extends State { }, onAccept: (DraggableData data) { if (widget.provider.onBeforeDropAccept != null) { - if (widget.provider.onBeforeDropAccept!( - data, widget.provider.controller, widget.newIndex) == - false) { + if (widget.provider.onBeforeDropAccept!(data, widget.provider.controller, widget.newIndex) == false) { setState(() { _over = false; _canDrop = false; @@ -92,12 +82,10 @@ class DropTabWidgetState extends State { } } if (widget.provider.controller == data.controller) { - widget.provider.controller - .reorderTab(data.tabData.index, widget.newIndex); + widget.provider.controller.reorderTab(data.tabData.index, widget.newIndex); } else { data.controller.removeTab(data.tabData.index); - widget.provider.controller - .insertTab(widget.newIndex, data.tabData); + widget.provider.controller.insertTab(widget.newIndex, data.tabData); } }, )); @@ -114,8 +102,7 @@ class _CustomPainter extends CustomPainter { Paint paint = Paint() ..color = dropColor ..style = PaintingStyle.fill; - canvas.drawRect( - Rect.fromLTWH(0, 0, DropTabWidget.dropWidth, size.height), paint); + canvas.drawRect(Rect.fromLTWH(0, 0, DropTabWidget.dropWidth, size.height), paint); } @override diff --git a/lib/src/internal/tabs_area/tabs_area_corner.dart b/lib/src/internal/tabs_area/tabs_area_corner.dart index ad9385e..6fee4c4 100644 --- a/lib/src/internal/tabs_area/tabs_area_corner.dart +++ b/lib/src/internal/tabs_area/tabs_area_corner.dart @@ -4,14 +4,14 @@ import 'package:tabbed_view/src/internal/tabbed_view_provider.dart'; import 'package:tabbed_view/src/internal/tabs_area/drop_tab_widget.dart'; import 'package:tabbed_view/src/internal/tabs_area/hidden_tabs.dart'; import 'package:tabbed_view/src/internal/tabs_area/tabs_area_buttons_widget.dart'; +import 'package:tabbed_view/tabbed_view.dart'; @internal class TabsAreaCorner extends StatelessWidget { final TabbedViewProvider provider; final HiddenTabs hiddenTabs; - const TabsAreaCorner( - {super.key, required this.provider, required this.hiddenTabs}); + const TabsAreaCorner({super.key, required this.provider, required this.hiddenTabs}); @override Widget build(BuildContext context) { @@ -19,20 +19,30 @@ class TabsAreaCorner extends StatelessWidget { } Widget _builder(BuildContext context, Widget? child) { - Widget corner = Container( - padding: EdgeInsets.only(left: DropTabWidget.dropWidth), - child: Row( - children: [ - TabsAreaButtonsWidget(provider: provider, hiddenTabs: hiddenTabs) - ], - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end)); - if (provider.controller.reorderEnable) { + TabbedViewThemeData theme = TabbedViewTheme.of(context); + TabsAreaThemeData tabsAreaTheme = theme.tabsArea; + TabThemeData tabTheme = theme.tab; + Widget areaButtons = Container( + margin: tabTheme.margin, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [TabsAreaButtonsWidget(provider: provider, hiddenTabs: hiddenTabs)], + ), + ); + if (tabsAreaTheme.addButton != null) { + return areaButtons; + } + if(provider.controller.reorderEnable) { return DropTabWidget( - provider: provider, - newIndex: provider.controller.length, - child: corner); + provider: provider, + newIndex: provider.controller.length, + child: Container( + padding: const EdgeInsets.only(left: DropTabWidget.dropWidth), + child: areaButtons, + ), + ); } - return corner; + return areaButtons; } } diff --git a/lib/src/tab_widget.dart b/lib/src/tab_widget.dart index 8d45c0b..b5a2da2 100644 --- a/lib/src/tab_widget.dart +++ b/lib/src/tab_widget.dart @@ -41,30 +41,30 @@ class TabWidget extends StatelessWidget { TabThemeData tabTheme = theme.tab; TabStatusThemeData statusTheme = tabTheme.getTabThemeFor(status); - List textAndButtons = _textAndButtons(context, tabTheme); + bool isMinWidthOrMaxWidth = tabTheme.minWidth != null || tabTheme.maxWidth != null; + + List textAndButtons = _textAndButtons(context, tabTheme, isMinWidthOrMaxWidth); Widget textAndButtonsContainer = ClipRect( - child: FlowLayout( - children: textAndButtons, - firstChildFlex: true, - verticalAlignment: tabTheme.verticalAlignment)); + child: isMinWidthOrMaxWidth + ? Row( + children: textAndButtons, + ) + : FlowLayout( + firstChildFlex: true, + verticalAlignment: tabTheme.verticalAlignment, + children: textAndButtons, + )); - BorderSide innerBottomBorder = statusTheme.innerBottomBorder ?? - tabTheme.innerBottomBorder ?? - BorderSide.none; - BorderSide innerTopBorder = statusTheme.innerTopBorder ?? - tabTheme.innerTopBorder ?? - BorderSide.none; + BorderSide innerBottomBorder = statusTheme.innerBottomBorder ?? tabTheme.innerBottomBorder ?? BorderSide.none; + BorderSide innerTopBorder = statusTheme.innerTopBorder ?? tabTheme.innerTopBorder ?? BorderSide.none; BoxDecoration? decoration = statusTheme.decoration ?? tabTheme.decoration; EdgeInsetsGeometry? padding; if (textAndButtons.length == 1) { - padding = - statusTheme.paddingWithoutButton ?? tabTheme.paddingWithoutButton; - } - if (padding == null) { - padding = statusTheme.padding ?? tabTheme.padding; + padding = statusTheme.paddingWithoutButton ?? tabTheme.paddingWithoutButton; } + padding ??= statusTheme.padding ?? tabTheme.padding; EdgeInsetsGeometry? margin = tabTheme.margin; if (statusTheme.margin != null) { @@ -72,14 +72,16 @@ class TabWidget extends StatelessWidget { } Widget tabWidget = Container( - child: Container( - child: textAndButtonsContainer, - padding: padding, - decoration: BoxDecoration( - border: - Border(top: innerTopBorder, bottom: innerBottomBorder))), - decoration: decoration, - margin: margin); + constraints: tabTheme.minWidth != null || tabTheme.maxWidth != null + ? BoxConstraints( + maxWidth: tabTheme.maxWidth ?? tabTheme.minWidth ?? double.infinity, + minWidth: tabTheme.minWidth ?? 0, + ) + : null, + decoration: decoration, + margin: margin, + child: Container(padding: padding, decoration: BoxDecoration(border: Border(top: innerTopBorder, bottom: innerBottomBorder)), child: textAndButtonsContainer), + ); MouseCursor cursor = MouseCursor.defer; if (provider.draggingTabIndex == null && status == TabStatus.selected) { @@ -98,17 +100,13 @@ class TabWidget extends StatelessWidget { if (tab.draggable) { DraggableConfig draggableConfig = DraggableConfig.defaultConfig; if (provider.onDraggableBuild != null) { - draggableConfig = - provider.onDraggableBuild!(provider.controller, index, tab); + draggableConfig = provider.onDraggableBuild!(provider.controller, index, tab); } if (draggableConfig.canDrag) { - Widget feedback = draggableConfig.feedback != null - ? draggableConfig.feedback! - : TabDragFeedbackWidget(tab: tab, tabTheme: tabTheme); + Widget feedback = draggableConfig.feedback != null ? draggableConfig.feedback! : TabDragFeedbackWidget(tab: tab, tabTheme: tabTheme); tabWidget = Draggable( - child: tabWidget, feedback: Material(child: feedback), data: DraggableData(provider.controller, tab), feedbackOffset: draggableConfig.feedbackOffset, @@ -140,51 +138,33 @@ class TabWidget extends StatelessWidget { if (draggableConfig.onDragCompleted != null) { draggableConfig.onDragCompleted!(); } - }); + }, + child: tabWidget); - tabWidget = Opacity( - child: tabWidget, - opacity: provider.draggingTabIndex != index - ? 1 - : tabTheme.draggingOpacity); + tabWidget = Opacity(opacity: provider.draggingTabIndex != index ? 1 : tabTheme.draggingOpacity, child: tabWidget); } } - if (provider.controller.reorderEnable && - provider.draggingTabIndex != tab.index) { - return DropTabWidget( - provider: provider, newIndex: tab.index, child: tabWidget); + if (provider.controller.reorderEnable && provider.draggingTabIndex != tab.index) { + return DropTabWidget(provider: provider, newIndex: tab.index, child: tabWidget); } return tabWidget; } /// Builds a list with title text and buttons. - List _textAndButtons(BuildContext context, TabThemeData tabTheme) { + List _textAndButtons(BuildContext context, TabThemeData tabTheme, bool isMinWidthOrMaxWidth) { List textAndButtons = []; TabData tab = provider.controller.tabs[index]; TabStatusThemeData statusTheme = tabTheme.getTabThemeFor(status); - Color normalColor = statusTheme.normalButtonColor != null - ? statusTheme.normalButtonColor! - : tabTheme.normalButtonColor; - Color hoverColor = statusTheme.hoverButtonColor != null - ? statusTheme.hoverButtonColor! - : tabTheme.hoverButtonColor; - Color disabledColor = statusTheme.disabledButtonColor != null - ? statusTheme.disabledButtonColor! - : tabTheme.disabledButtonColor; + Color normalColor = statusTheme.normalButtonColor != null ? statusTheme.normalButtonColor! : tabTheme.normalButtonColor; + Color hoverColor = statusTheme.hoverButtonColor != null ? statusTheme.hoverButtonColor! : tabTheme.hoverButtonColor; + Color disabledColor = statusTheme.disabledButtonColor != null ? statusTheme.disabledButtonColor! : tabTheme.disabledButtonColor; - BoxDecoration? normalBackground = statusTheme.normalButtonBackground != null - ? statusTheme.normalButtonBackground - : tabTheme.normalButtonBackground; - BoxDecoration? hoverBackground = statusTheme.hoverButtonBackground != null - ? statusTheme.hoverButtonBackground - : tabTheme.hoverButtonBackground; - BoxDecoration? disabledBackground = - statusTheme.disabledButtonBackground != null - ? statusTheme.disabledButtonBackground - : tabTheme.disabledButtonBackground; + BoxDecoration? normalBackground = statusTheme.normalButtonBackground ?? tabTheme.normalButtonBackground; + BoxDecoration? hoverBackground = statusTheme.hoverButtonBackground ?? tabTheme.hoverButtonBackground; + BoxDecoration? disabledBackground = statusTheme.disabledButtonBackground ?? tabTheme.disabledButtonBackground; TextStyle? textStyle = tabTheme.textStyle; if (statusTheme.fontColor != null) { @@ -195,10 +175,8 @@ class TabWidget extends StatelessWidget { } } - final bool buttonsEnabled = provider.draggingTabIndex == null && - (provider.selectToEnableButtons == false || - (provider.selectToEnableButtons && status == TabStatus.selected)); - bool hasButtons = tab.buttons != null && tab.buttons!.length > 0; + final bool buttonsEnabled = provider.draggingTabIndex == null && (provider.selectToEnableButtons == false || (provider.selectToEnableButtons && status == TabStatus.selected)); + bool hasButtons = tab.buttons != null && tab.buttons!.isNotEmpty; EdgeInsets? padding; if (tab.closable || hasButtons && tabTheme.buttonsOffset > 0) { padding = EdgeInsets.only(right: tabTheme.buttonsOffset); @@ -211,10 +189,8 @@ class TabWidget extends StatelessWidget { } } - textAndButtons.add(Container( - child: - Text(tab.text, style: textStyle, overflow: TextOverflow.ellipsis), - padding: padding)); + Widget text = Container(padding: padding, child: Text(tab.text, style: textStyle, overflow: TextOverflow.ellipsis)); + textAndButtons.add(isMinWidthOrMaxWidth ? Expanded(child: text) : text); if (hasButtons) { for (int i = 0; i < tab.buttons!.length; i++) { @@ -224,6 +200,7 @@ class TabWidget extends StatelessWidget { } TabButton button = tab.buttons![i]; textAndButtons.add(Container( + padding: padding, child: TabButtonWidget( provider: provider, button: button, @@ -234,11 +211,8 @@ class TabWidget extends StatelessWidget { normalBackground: normalBackground, hoverBackground: hoverBackground, disabledBackground: disabledBackground, - iconSize: button.iconSize != null - ? button.iconSize! - : tabTheme.buttonIconSize, - themePadding: tabTheme.buttonPadding), - padding: padding)); + iconSize: button.iconSize != null ? button.iconSize! : tabTheme.buttonIconSize, + themePadding: tabTheme.buttonPadding))); } } if (tab.closable) { @@ -246,33 +220,33 @@ class TabWidget extends StatelessWidget { if (hasButtons && tabTheme.buttonsGap > 0) { padding = EdgeInsets.only(left: tabTheme.buttonsGap); } - TabButton closeButton = TabButton( - icon: tabTheme.closeIcon, - onPressed: () => _onClose(context, index), - toolTip: provider.closeButtonTooltip); + TabButton closeButton = TabButton(icon: tabTheme.closeIcon, onPressed: () => _onClose(context, index), toolTip: provider.closeButtonTooltip); - textAndButtons.add(Container( + textAndButtons.add( + Container( + padding: padding, child: TabButtonWidget( - provider: provider, - button: closeButton, - enabled: buttonsEnabled, - normalColor: normalColor, - hoverColor: hoverColor, - disabledColor: disabledColor, - normalBackground: normalBackground, - hoverBackground: hoverBackground, - disabledBackground: disabledBackground, - iconSize: tabTheme.buttonIconSize, - themePadding: tabTheme.buttonPadding), - padding: padding)); + provider: provider, + button: closeButton, + enabled: buttonsEnabled, + normalColor: normalColor, + hoverColor: hoverColor, + disabledColor: disabledColor, + normalBackground: normalBackground, + hoverBackground: hoverBackground, + disabledBackground: disabledBackground, + iconSize: tabTheme.buttonIconSize, + themePadding: tabTheme.buttonPadding, + ), + ), + ); } return textAndButtons; } void _onClose(BuildContext context, int index) { - if (provider.tabCloseInterceptor == null || - provider.tabCloseInterceptor!(index)) { + if (provider.tabCloseInterceptor == null || provider.tabCloseInterceptor!(index)) { onClose(); TabData tabData = provider.controller.removeTab(index); if (provider.onTabClose != null) { @@ -282,8 +256,7 @@ class TabWidget extends StatelessWidget { } void _onSelect(BuildContext context, int newTabIndex) { - if (provider.tabSelectInterceptor == null || - provider.tabSelectInterceptor!(newTabIndex)) { + if (provider.tabSelectInterceptor == null || provider.tabSelectInterceptor!(newTabIndex)) { provider.controller.selectedIndex = newTabIndex; } } diff --git a/lib/src/tabbed_view_menu_widget.dart b/lib/src/tabbed_view_menu_widget.dart index af49075..98c536a 100644 --- a/lib/src/tabbed_view_menu_widget.dart +++ b/lib/src/tabbed_view_menu_widget.dart @@ -6,7 +6,7 @@ import 'package:tabbed_view/src/theme/theme_widget.dart'; /// Widget for menu. class TabbedViewMenuWidget extends StatefulWidget { - const TabbedViewMenuWidget({required this.provider}); + const TabbedViewMenuWidget({super.key, required this.provider}); final TabbedViewProvider provider; @@ -20,51 +20,51 @@ class _TabbedViewMenuWidgetState extends State { Widget build(BuildContext context) { TabbedViewThemeData theme = TabbedViewTheme.of(context); TabbedViewMenuThemeData menuTheme = theme.menu; - bool hasDivider = - menuTheme.dividerThickness > 0 && menuTheme.dividerColor != null; + bool hasDivider = menuTheme.dividerThickness > 0 && menuTheme.dividerColor != null; int itemCount = widget.provider.menuItems.length; if (hasDivider) { itemCount += widget.provider.menuItems.length - 1; } ListView list = ListView.builder( - itemCount: itemCount, - itemBuilder: (BuildContext context, int index) { - int itemIndex = index; - if (hasDivider) { - itemIndex = index ~/ 2; - if (index.isOdd) { - return Divider( - height: menuTheme.dividerThickness, - color: menuTheme.dividerColor, - thickness: menuTheme.dividerThickness); - } + itemCount: itemCount, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + int itemIndex = index; + if (hasDivider) { + itemIndex = index ~/ 2; + if (index.isOdd) { + return Divider(height: menuTheme.dividerThickness, color: menuTheme.dividerColor, thickness: menuTheme.dividerThickness); } - return InkWell( - child: Container( - padding: menuTheme.menuItemPadding, - child: Text(widget.provider.menuItems[itemIndex].text, - overflow: menuTheme.ellipsisOverflowText - ? TextOverflow.ellipsis - : null)), - hoverColor: menuTheme.hoverColor, - onTap: () { - widget.provider.menuItemsUpdater([]); - Function? onSelection = - widget.provider.menuItems[itemIndex].onSelection; - if (onSelection != null) { - onSelection(); - } - }); - }); + } + return InkWell( + hoverColor: menuTheme.hoverColor, + onTap: () { + widget.provider.menuItemsUpdater([]); + Function? onSelection = widget.provider.menuItems[itemIndex].onSelection; + if (onSelection != null) { + onSelection(); + } + }, + child: Container( + padding: menuTheme.menuItemPadding, + child: Text( + widget.provider.menuItems[itemIndex].text, + overflow: menuTheme.ellipsisOverflowText ? TextOverflow.ellipsis : null, + ), + ), + ); + }, + ); return Container( - margin: menuTheme.margin, - padding: menuTheme.padding, - child: Material( - child: list, - textStyle: menuTheme.textStyle, - color: Colors.transparent), - decoration: - BoxDecoration(color: menuTheme.color, border: menuTheme.border)); + margin: menuTheme.margin, + padding: menuTheme.padding, + decoration: menuTheme.decoration, + child: Material( + type: MaterialType.transparency, + textStyle: menuTheme.textStyle, + child: list, + ), + ); } } diff --git a/lib/src/tabs_area.dart b/lib/src/tabs_area.dart index d6f10bc..faf0e63 100644 --- a/lib/src/tabs_area.dart +++ b/lib/src/tabs_area.dart @@ -9,6 +9,10 @@ import 'package:tabbed_view/src/tabs_area_layout.dart'; import 'package:tabbed_view/src/theme/tabbed_view_theme_data.dart'; import 'package:tabbed_view/src/theme/tabs_area_theme_data.dart'; import 'package:tabbed_view/src/theme/theme_widget.dart'; +import 'package:tabbed_view/src/theme/tab_theme_data.dart'; +import 'package:tabbed_view/src/tab_button.dart'; +import 'package:tabbed_view/src/tab_button_widget.dart'; +import 'package:tabbed_view/src/internal/tabs_area/drop_tab_widget.dart'; /// Widget for the tabs and buttons. class TabsArea extends StatefulWidget { @@ -31,6 +35,8 @@ class _TabsAreaState extends State { TabbedViewController controller = widget.provider.controller; TabbedViewThemeData theme = TabbedViewTheme.of(context); TabsAreaThemeData tabsAreaTheme = theme.tabsArea; + TabThemeData tabTheme = theme.tab; + List children = []; for (int index = 0; index < controller.tabs.length; index++) { TabStatus status = _getStatusFor(index); @@ -43,22 +49,57 @@ class _TabsAreaState extends State { onClose: _onTabClose)); } - children.add( - TabsAreaCorner(provider: widget.provider, hiddenTabs: _hiddenTabs)); + bool isAddButton = false; + if (tabsAreaTheme.addButton != null) { + isAddButton = true; + final TabButton tabButton = tabsAreaTheme.addButton!; + Widget addButton = DropTabWidget( + provider: widget.provider, + newIndex: widget.provider.controller.length, + child: Container( + padding: const EdgeInsets.only(left: DropTabWidget.dropWidth), + child: TabButtonWidget( + provider: widget.provider, + button: tabButton, + enabled: widget.provider.draggingTabIndex == null, + normalColor: tabsAreaTheme.normalButtonColor, + hoverColor: tabsAreaTheme.hoverButtonColor, + disabledColor: tabsAreaTheme.disabledButtonColor, + normalBackground: tabsAreaTheme.normalButtonBackground, + hoverBackground: tabsAreaTheme.hoverButtonBackground, + disabledBackground: tabsAreaTheme.disabledButtonBackground, + iconSize: tabButton.iconSize != null ? tabButton.iconSize! : tabsAreaTheme.buttonIconSize, + themePadding: tabsAreaTheme.buttonPadding, + ), + ), + ); + children.add( + Container( + margin: tabTheme.margin, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [addButton], + ), + ), + ); + } + + children.add(TabsAreaCorner(provider: widget.provider, hiddenTabs: _hiddenTabs)); Widget tabsAreaLayout = TabsAreaLayout( - children: children, - theme: theme, - hiddenTabs: _hiddenTabs, - selectedTabIndex: controller.selectedIndex); + theme: theme, + hiddenTabs: _hiddenTabs, + isAddButton: isAddButton, + selectedTabIndex: controller.selectedIndex, + children: children, + ); tabsAreaLayout = ClipRect(child: tabsAreaLayout); Decoration? decoration; if (tabsAreaTheme.color != null || tabsAreaTheme.border != null) { - decoration = BoxDecoration( - color: tabsAreaTheme.color, border: tabsAreaTheme.border); + decoration = BoxDecoration(color: tabsAreaTheme.color, border: tabsAreaTheme.border); } - return Container(child: tabsAreaLayout, decoration: decoration); + return Container(decoration: decoration, child: tabsAreaLayout); } /// Gets the status of the tab for a given index. @@ -68,8 +109,7 @@ class _TabsAreaState extends State { throw Exception('Invalid tab index: $tabIndex'); } - if (controller.selectedIndex != null && - controller.selectedIndex == tabIndex) { + if (controller.selectedIndex != null && controller.selectedIndex == tabIndex) { return TabStatus.selected; } else if (_highlightedIndex != null && _highlightedIndex == tabIndex) { return TabStatus.highlighted; diff --git a/lib/src/tabs_area_layout.dart b/lib/src/tabs_area_layout.dart index b180ee8..db75fc7 100644 --- a/lib/src/tabs_area_layout.dart +++ b/lib/src/tabs_area_layout.dart @@ -13,17 +13,19 @@ import 'package:tabbed_view/src/theme/tabs_area_theme_data.dart'; /// Displays the popup menu button for tabs hidden due to lack of space. /// The selected [TabWidget] will always be visible. class TabsAreaLayout extends MultiChildRenderObjectWidget { - TabsAreaLayout( - {Key? key, - required List children, - required this.theme, - required this.hiddenTabs, - required this.selectedTabIndex}) - : super(key: key, children: children); + TabsAreaLayout({ + Key? key, + required List children, + required this.theme, + required this.hiddenTabs, + required this.selectedTabIndex, + required this.isAddButton, + }) : super(key: key, children: children); final TabbedViewThemeData theme; final HiddenTabs hiddenTabs; final int? selectedTabIndex; + final bool isAddButton; @override _TabsAreaLayoutElement createElement() { @@ -32,15 +34,15 @@ class TabsAreaLayout extends MultiChildRenderObjectWidget { @override _TabsAreaLayoutRenderBox createRenderObject(BuildContext context) { - return _TabsAreaLayoutRenderBox(theme, hiddenTabs, selectedTabIndex); + return _TabsAreaLayoutRenderBox(theme, hiddenTabs, selectedTabIndex, isAddButton); } @override - void updateRenderObject( - BuildContext context, _TabsAreaLayoutRenderBox renderObject) { + void updateRenderObject(BuildContext context, _TabsAreaLayoutRenderBox renderObject) { renderObject..tabsAreaTheme = theme.tabsArea; renderObject..hiddenTabs = hiddenTabs; renderObject..selectedTabIndex = selectedTabIndex; + renderObject..isAddButton = isAddButton; //renderObject.markNeedsLayoutForSizedByParentChange() } @@ -54,8 +56,7 @@ class _TabsAreaLayoutElement extends MultiChildRenderObjectElement { void debugVisitOnstageChildren(ElementVisitor visitor) { children.forEach((child) { if (child.renderObject != null) { - TabsAreaLayoutParentData parentData = - child.renderObject!.parentData as TabsAreaLayoutParentData; + TabsAreaLayoutParentData parentData = child.renderObject!.parentData as TabsAreaLayoutParentData; if (parentData.visible) { visitor(child); } @@ -65,21 +66,19 @@ class _TabsAreaLayoutElement extends MultiChildRenderObjectElement { } /// The [TabsAreaLayout] render box. -class _TabsAreaLayoutRenderBox extends RenderBox - with - ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin { - _TabsAreaLayoutRenderBox( - TabbedViewThemeData theme, HiddenTabs hiddenTabs, int? selectedTabIndex) - : this._tabsAreaTheme = theme.tabsArea, - this._hiddenTabs = hiddenTabs, - this._selectedTabIndex = selectedTabIndex; +class _TabsAreaLayoutRenderBox extends RenderBox with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { + _TabsAreaLayoutRenderBox(TabbedViewThemeData theme, HiddenTabs hiddenTabs, int? selectedTabIndex, bool isAddButton) + : _tabsAreaTheme = theme.tabsArea, + _hiddenTabs = hiddenTabs, + _selectedTabIndex = selectedTabIndex, + _isAddButton = isAddButton; int? _selectedTabIndex; int? get selectedTabIndex => _selectedTabIndex; late RenderBox _corner; + late RenderBox? _addButton; set selectedTabIndex(int? value) { if (_selectedTabIndex != value) { @@ -109,6 +108,16 @@ class _TabsAreaLayoutRenderBox extends RenderBox } } + bool _isAddButton; + + bool get isAddButton => _isAddButton; + + set isAddButton(bool value) { + if (_isAddButton != value) { + _isAddButton = value; + } + } + @override void setupParentData(RenderBox child) { if (child.parentData is! TabsAreaLayoutParentData) { @@ -118,8 +127,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox @override void performLayout() { - final BoxConstraints childConstraints = - BoxConstraints.loose(Size(double.infinity, constraints.maxHeight)); + final BoxConstraints childConstraints = BoxConstraints.loose(Size(double.infinity, constraints.maxHeight)); List children = []; visitChildren((child) { @@ -129,6 +137,10 @@ class _TabsAreaLayoutRenderBox extends RenderBox // There will always be at least 1 child (corner area). _corner = children.removeLast(); + if (isAddButton) { + _addButton = children.removeLast(); + } + // Defines the biggest height to avoid displacement of tabs when // changing visibility. double height = 0; @@ -137,37 +149,34 @@ class _TabsAreaLayoutRenderBox extends RenderBox final double minCornerAreaWidth = _corner.size.width; height = math.max(height, _corner.size.height); + _addButton?.layout(childConstraints, parentUsesSize: true); + final double minAddButtonWidth = _addButton?.size.width ?? 0; + height = math.max(height, _addButton?.size.height ?? 0); + // layout all (tabs + tabs area buttons) VisibleTabs visibleTabs = VisibleTabs(tabsAreaTheme); for (int i = 0; i < children.length; i++) { final RenderBox child = children[i]; - final TabsAreaLayoutParentData parentData = - child.tabsAreaLayoutParentData(); + final TabsAreaLayoutParentData parentData = child.tabsAreaLayoutParentData(); parentData.reset(); - parentData.selected = - selectedTabIndex != null && selectedTabIndex == i ? true : false; + parentData.selected = selectedTabIndex != null && selectedTabIndex == i ? true : false; child.layout(childConstraints, parentUsesSize: true); height = math.max(height, child.size.height); visibleTabs.add(child); } - if (tabsAreaTheme.gapBottomBorder.style == BorderStyle.solid && - tabsAreaTheme.gapBottomBorder.width > 0) { + if (tabsAreaTheme.gapBottomBorder.style == BorderStyle.solid && tabsAreaTheme.gapBottomBorder.width > 0) { height = math.max(height, tabsAreaTheme.gapBottomBorder.width); } - double availableWidth = math.max( - constraints.maxWidth - tabsAreaTheme.initialGap - _corner.size.width, - 0); + double availableWidth = math.max(constraints.maxWidth - tabsAreaTheme.initialGap - _corner.size.width - (_addButton?.size.width ?? 0), 0); - visibleTabs.layoutSingleTab( - constraints.maxWidth, height, _corner.size.width); + visibleTabs.layoutSingleTab(constraints.maxWidth, height, (_corner.size.width + (_addButton?.size.width ?? 0))); List hiddenIndexes = []; - while (visibleTabs.length > 0 && - visibleTabs.requiredTotalWidth() > availableWidth) { + while (visibleTabs.length > 0 && visibleTabs.requiredTotalWidth() > availableWidth) { int? removedIndex; if (visibleTabs.length == 1) { visibleTabs.removeFirst(); @@ -178,53 +187,47 @@ class _TabsAreaLayoutRenderBox extends RenderBox if (removedIndex != null) { hiddenIndexes.add(removedIndex); } - visibleTabs.layoutSingleTab( - constraints.maxWidth, height, _corner.size.width); + visibleTabs.layoutSingleTab(constraints.maxWidth, height, (_corner.size.width + (_addButton?.size.width ?? 0))); } hiddenTabs.update(hiddenIndexes); visibleTabs.updateOffsets(); - _corner.layout( - BoxConstraints.tightFor( - width: math.max( - constraints.maxWidth - visibleTabs.maxX(), minCornerAreaWidth), - height: height), - parentUsesSize: true); + _corner.layout(BoxConstraints.tightFor(width: math.max(constraints.maxWidth - visibleTabs.maxX(), minCornerAreaWidth), height: height), parentUsesSize: true); + + _addButton?.layout(BoxConstraints.tightFor(width: minAddButtonWidth, height: height), parentUsesSize: true); List visibleChildren = []; for (int i = 0; i < visibleTabs.length; i++) { RenderBox tab = visibleTabs.get(i); - final TabsAreaLayoutParentData tabParentData = - tab.tabsAreaLayoutParentData(); + final TabsAreaLayoutParentData tabParentData = tab.tabsAreaLayoutParentData(); tabParentData.visible = true; visibleChildren.add(tab); } - final TabsAreaLayoutParentData cornerParentData = - _corner.tabsAreaLayoutParentData(); + final TabsAreaLayoutParentData cornerParentData = _corner.tabsAreaLayoutParentData(); // anchoring corner to the right - cornerParentData.offset = Offset(constraints.maxWidth - _corner.size.width, - constraints.maxHeight - _corner.size.height); + cornerParentData.offset = Offset(constraints.maxWidth - _corner.size.width, constraints.maxHeight - _corner.size.height); cornerParentData.visible = true; visibleChildren.add(_corner); + if (_addButton != null) { + final TabsAreaLayoutParentData addButtonParentData = _addButton!.tabsAreaLayoutParentData(); + // anchoring corner to the right + addButtonParentData.offset = Offset(constraints.maxWidth - _corner.size.width, constraints.maxHeight - _addButton!.size.height); + + addButtonParentData.visible = true; + visibleChildren.add(_addButton!); + } + if (tabsAreaTheme.equalHeights == EqualHeights.none) { // Aligning and fix max height on visible children. for (RenderBox tab in visibleChildren) { - final TabsAreaLayoutParentData parentData = - tab.tabsAreaLayoutParentData(); - tab.layout( - BoxConstraints( - minWidth: tab.size.width, - maxWidth: tab.size.width, - minHeight: tab.size.height, - maxHeight: tab.size.height), - parentUsesSize: true); - parentData.offset = - Offset(parentData.offset.dx, height - tab.size.height); + final TabsAreaLayoutParentData parentData = tab.tabsAreaLayoutParentData(); + tab.layout(BoxConstraints(minWidth: tab.size.width, maxWidth: tab.size.width, minHeight: tab.size.height, maxHeight: tab.size.height), parentUsesSize: true); + parentData.offset = Offset(parentData.offset.dx, height - tab.size.height); } } else { if (tabsAreaTheme.equalHeights == EqualHeights.tabs) { @@ -235,28 +238,20 @@ class _TabsAreaLayoutRenderBox extends RenderBox } for (int i = 0; i < visibleCount; i++) { RenderBox tab = visibleChildren[i]; - tab.layout( - BoxConstraints.tightFor(width: tab.size.width, height: height), - parentUsesSize: true); - final TabsAreaLayoutParentData parentData = - tab.tabsAreaLayoutParentData(); + tab.layout(BoxConstraints.tightFor(width: tab.size.width, height: height), parentUsesSize: true); + final TabsAreaLayoutParentData parentData = tab.tabsAreaLayoutParentData(); parentData.offset = Offset(parentData.offset.dx, 0); } if (hiddenTabs.hasHiddenTabs) { RenderBox corner = visibleChildren.last; - final TabsAreaLayoutParentData parentData = - corner.tabsAreaLayoutParentData(); - parentData.offset = - Offset(parentData.offset.dx, height - corner.size.height); + final TabsAreaLayoutParentData parentData = corner.tabsAreaLayoutParentData(); + parentData.offset = Offset(parentData.offset.dx, height - corner.size.height); } } else if (tabsAreaTheme.equalHeights == EqualHeights.all) { for (RenderBox child in visibleChildren) { - child.layout( - BoxConstraints.tightFor(width: child.size.width, height: height), - parentUsesSize: true); + child.layout(BoxConstraints.tightFor(width: child.size.width, height: height), parentUsesSize: true); - final TabsAreaLayoutParentData parentData = - child.tabsAreaLayoutParentData(); + final TabsAreaLayoutParentData parentData = child.tabsAreaLayoutParentData(); parentData.offset = Offset(parentData.offset.dx, 0); } } @@ -284,9 +279,8 @@ class _TabsAreaLayoutRenderBox extends RenderBox RenderBox? selectedTab; TabsAreaLayoutParentData? selectedTabParentData; visitVisibleChildren((RenderObject child) { - final TabsAreaLayoutParentData childParentData = - child.tabsAreaLayoutParentData(); - if (child != _corner) { + final TabsAreaLayoutParentData childParentData = child.tabsAreaLayoutParentData(); + if (child != _corner && child != _addButton) { if (childParentData.selected) { selectedTab = child as RenderBox; selectedTabParentData = childParentData; @@ -305,8 +299,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox Canvas canvas = context.canvas; Paint? gapBorderPaint; - if (tabsAreaTheme.gapBottomBorder.style == BorderStyle.solid && - tabsAreaTheme.gapBottomBorder.width > 0) { + if (tabsAreaTheme.gapBottomBorder.style == BorderStyle.solid && tabsAreaTheme.gapBottomBorder.width > 0) { gapBorderPaint = Paint() ..style = PaintingStyle.fill ..color = tabsAreaTheme.gapBottomBorder.color; @@ -317,10 +310,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox // initial gap if (tabsAreaTheme.initialGap > 0 && gapBorderPaint != null) { - canvas.drawRect( - Rect.fromLTWH(left, topGap, tabsAreaTheme.initialGap, - tabsAreaTheme.gapBottomBorder.width), - gapBorderPaint); + canvas.drawRect(Rect.fromLTWH(left, topGap, tabsAreaTheme.initialGap, tabsAreaTheme.gapBottomBorder.width), gapBorderPaint); } left += tabsAreaTheme.initialGap; @@ -332,10 +322,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox // right gap if (tabsAreaTheme.middleGap > 0 && gapBorderPaint != null) { - canvas.drawRect( - Rect.fromLTWH(left, topGap, tabsAreaTheme.middleGap, - tabsAreaTheme.gapBottomBorder.width), - gapBorderPaint); + canvas.drawRect(Rect.fromLTWH(left, topGap, tabsAreaTheme.middleGap, tabsAreaTheme.gapBottomBorder.width), gapBorderPaint); } left += tabsAreaTheme.middleGap; } @@ -346,8 +333,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox double lastX; if (visibleTabs.length > 0) { RenderBox lastTab = visibleTabs.last; - TabsAreaLayoutParentData tabParentData = - lastTab.tabsAreaLayoutParentData(); + TabsAreaLayoutParentData tabParentData = lastTab.tabsAreaLayoutParentData(); lastX = offset.dx + tabParentData.offset.dx + lastTab.size.width; } else { lastX = offset.dx + tabsAreaTheme.initialGap; @@ -357,11 +343,11 @@ class _TabsAreaLayoutRenderBox extends RenderBox if (_corner.tabsAreaLayoutParentData().visible) { // lastGapWidth -= _corner.size.width; } + if(_addButton?.tabsAreaLayoutParentData().visible ?? false){ + // lastGapWidth -= _addButton!.size.width; + } if (lastGapWidth > 0) { - canvas.drawRect( - Rect.fromLTWH(lastX, topGap, lastGapWidth, - tabsAreaTheme.gapBottomBorder.width), - gapBorderPaint); + canvas.drawRect(Rect.fromLTWH(lastX, topGap, lastGapWidth, tabsAreaTheme.gapBottomBorder.width), gapBorderPaint); } } } @@ -371,8 +357,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox List visibleTabs = []; visitVisibleChildren((renderObject) { final RenderBox child = renderObject as RenderBox; - final TabsAreaLayoutParentData childParentData = - child.tabsAreaLayoutParentData(); + final TabsAreaLayoutParentData childParentData = child.tabsAreaLayoutParentData(); if (childParentData.selected) { visibleTabs.insert(0, child); } else { @@ -383,8 +368,7 @@ class _TabsAreaLayoutRenderBox extends RenderBox bool hitTest = false; visibleTabs.forEach((child) { if (!hitTest) { - final TabsAreaLayoutParentData childParentData = - child.tabsAreaLayoutParentData(); + final TabsAreaLayoutParentData childParentData = child.tabsAreaLayoutParentData(); hitTest = result.addWithPaintOffset( offset: childParentData.offset, position: position, @@ -403,6 +387,6 @@ class _TabsAreaLayoutRenderBox extends RenderBox /// Utility extension to facilitate obtaining parent data. extension _TabsAreaLayoutParentDataGetter on RenderObject { TabsAreaLayoutParentData tabsAreaLayoutParentData() { - return this.parentData as TabsAreaLayoutParentData; + return parentData as TabsAreaLayoutParentData; } } diff --git a/lib/src/theme/default_themes/classic_theme.dart b/lib/src/theme/default_themes/classic_theme.dart index 433e833..8617da7 100644 --- a/lib/src/theme/default_themes/classic_theme.dart +++ b/lib/src/theme/default_themes/classic_theme.dart @@ -8,10 +8,7 @@ import 'package:tabbed_view/src/theme/tabs_area_theme_data.dart'; /// Predefined classic theme builder. class ClassicTheme { - static TabbedViewThemeData build( - {required MaterialColor colorSet, - required double fontSize, - required Color borderColor}) { + static TabbedViewThemeData build({required MaterialColor colorSet, required double fontSize, required Color borderColor}) { Color backgroundColor = colorSet[50]!; Color highlightedColor = colorSet[300]!; Color fontColor = colorSet[900]!; @@ -39,14 +36,8 @@ class ClassicTheme { fontSize: fontSize, backgroundColor: backgroundColor, highlightedColor: highlightedColor), - contentArea: contentAreaTheme( - borderColor: borderColor, backgroundColor: backgroundColor), - menu: menuTheme( - hoverColor: menuHoverColor, - color: backgroundColor, - borderColor: borderColor, - fontSize: fontSize, - fontColor: fontColor)); + contentArea: contentAreaTheme(borderColor: borderColor, backgroundColor: backgroundColor), + menu: menuTheme(hoverColor: menuHoverColor, color: backgroundColor, borderColor: borderColor, fontSize: fontSize, fontColor: fontColor)); } static TabsAreaThemeData tabsAreaTheme( @@ -64,9 +55,7 @@ class ClassicTheme { disabledButtonColor: disabledButtonColor, buttonPadding: const EdgeInsets.all(2), hoverButtonBackground: BoxDecoration(color: highlightedColor), - buttonsAreaDecoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor, width: 1)), + buttonsAreaDecoration: BoxDecoration(color: backgroundColor, border: Border.all(color: borderColor, width: 1)), buttonsAreaPadding: EdgeInsets.all(2), middleGap: -1, gapBottomBorder: BorderSide(color: borderColor, width: 1)); @@ -91,53 +80,32 @@ class ClassicTheme { buttonPadding: const EdgeInsets.all(2), padding: EdgeInsets.fromLTRB(6, 3, 3, 3), paddingWithoutButton: EdgeInsets.fromLTRB(6, 3, 6, 3), - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor, width: 1)), - draggingDecoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor, width: 1)), - highlightedStatus: TabStatusThemeData( - decoration: BoxDecoration( - color: highlightedColor, - border: Border.all(color: borderColor, width: 1))), + decoration: BoxDecoration(color: backgroundColor, border: Border.all(color: borderColor, width: 1)), + draggingDecoration: BoxDecoration(color: backgroundColor, border: Border.all(color: borderColor, width: 1)), + highlightedStatus: TabStatusThemeData(decoration: BoxDecoration(color: highlightedColor, border: Border.all(color: borderColor, width: 1))), selectedStatus: TabStatusThemeData( decoration: BoxDecoration( - color: backgroundColor, - border: Border( - left: BorderSide(color: borderColor, width: 1), - top: BorderSide(color: borderColor, width: 1), - right: BorderSide(color: borderColor, width: 1))), + color: backgroundColor, border: Border(left: BorderSide(color: borderColor, width: 1), top: BorderSide(color: borderColor, width: 1), right: BorderSide(color: borderColor, width: 1))), padding: EdgeInsets.fromLTRB(6, 3, 3, 8), )); } - static ContentAreaThemeData contentAreaTheme( - {required Color borderColor, required Color backgroundColor}) { + static ContentAreaThemeData contentAreaTheme({required Color borderColor, required Color backgroundColor}) { BorderSide borderSide = BorderSide(width: 1, color: borderColor); - BoxDecoration decoration = BoxDecoration( - color: backgroundColor, - border: - Border(bottom: borderSide, left: borderSide, right: borderSide)); - BoxDecoration decorationNoTabsArea = BoxDecoration( - color: backgroundColor, - border: Border.all(width: 1, color: borderColor)); - return ContentAreaThemeData( - decoration: decoration, decorationNoTabsArea: decorationNoTabsArea); + BoxDecoration decoration = BoxDecoration(color: backgroundColor, border: Border(bottom: borderSide, left: borderSide, right: borderSide)); + BoxDecoration decorationNoTabsArea = BoxDecoration(color: backgroundColor, border: Border.all(width: 1, color: borderColor)); + return ContentAreaThemeData(decoration: decoration, decorationNoTabsArea: decorationNoTabsArea); } - static TabbedViewMenuThemeData menuTheme( - {required Color fontColor, - required double fontSize, - required Color color, - required hoverColor, - required borderColor}) { + static TabbedViewMenuThemeData menuTheme({required Color fontColor, required double fontSize, required Color color, required hoverColor, required borderColor}) { return TabbedViewMenuThemeData( textStyle: TextStyle(fontSize: fontSize, color: fontColor), - border: Border.all(width: 1, color: borderColor), + decoration: BoxDecoration( + border: Border.all(width: 1, color: borderColor), + color: color, + ), margin: EdgeInsets.all(8), menuItemPadding: EdgeInsets.all(8), - color: color, hoverColor: hoverColor, dividerColor: borderColor, dividerThickness: 1); diff --git a/lib/src/theme/default_themes/dark_theme.dart b/lib/src/theme/default_themes/dark_theme.dart index d624326..2a78666 100644 --- a/lib/src/theme/default_themes/dark_theme.dart +++ b/lib/src/theme/default_themes/dark_theme.dart @@ -129,7 +129,9 @@ class DarkTheme { textStyle: TextStyle(fontSize: fontSize, color: fontColor), margin: EdgeInsets.all(8), menuItemPadding: EdgeInsets.all(8), - color: color, + decoration: BoxDecoration( + color: color, + ), hoverColor: hoverColor, dividerColor: dividerColor, dividerThickness: 1); diff --git a/lib/src/theme/default_themes/minimalist_theme.dart b/lib/src/theme/default_themes/minimalist_theme.dart index a35afd1..06515f6 100644 --- a/lib/src/theme/default_themes/minimalist_theme.dart +++ b/lib/src/theme/default_themes/minimalist_theme.dart @@ -10,11 +10,7 @@ import 'package:tabbed_view/src/theme/tabs_area_theme_data.dart'; /// Predefined minimalist theme builder. class MinimalistTheme { static TabbedViewThemeData build({required MaterialColor colorSet}) { - return TabbedViewThemeData( - tabsArea: tabsAreaTheme(colorSet: colorSet), - tab: tabTheme(colorSet: colorSet), - contentArea: contentAreaTheme(colorSet: colorSet), - menu: menuTheme(colorSet: colorSet)); + return TabbedViewThemeData(tabsArea: tabsAreaTheme(colorSet: colorSet), tab: tabTheme(colorSet: colorSet), contentArea: contentAreaTheme(colorSet: colorSet), menu: menuTheme(colorSet: colorSet)); } static TabsAreaThemeData tabsAreaTheme({required MaterialColor colorSet}) { @@ -43,8 +39,7 @@ class MinimalistTheme { disabledButtonColor: colorSet[400]!, buttonPadding: const EdgeInsets.all(2), hoverButtonBackground: BoxDecoration(color: colorSet[300]!), - highlightedStatus: - TabStatusThemeData(decoration: BoxDecoration(color: colorSet[300]!)), + highlightedStatus: TabStatusThemeData(decoration: BoxDecoration(color: colorSet[300]!)), selectedStatus: TabStatusThemeData( normalButtonColor: colorSet[50]!, hoverButtonColor: colorSet[50]!, @@ -55,27 +50,22 @@ class MinimalistTheme { ); } - static ContentAreaThemeData contentAreaTheme( - {required MaterialColor colorSet}) { + static ContentAreaThemeData contentAreaTheme({required MaterialColor colorSet}) { BorderSide borderSide = BorderSide(width: 1, color: colorSet[900]!); - BoxDecoration decoration = BoxDecoration( - color: colorSet[50]!, - border: - Border(bottom: borderSide, left: borderSide, right: borderSide)); - BoxDecoration decorationNoTabsArea = BoxDecoration( - color: colorSet[50]!, - border: Border.all(width: 1, color: colorSet[900]!)); - return ContentAreaThemeData( - decoration: decoration, decorationNoTabsArea: decorationNoTabsArea); + BoxDecoration decoration = BoxDecoration(color: colorSet[50]!, border: Border(bottom: borderSide, left: borderSide, right: borderSide)); + BoxDecoration decorationNoTabsArea = BoxDecoration(color: colorSet[50]!, border: Border.all(width: 1, color: colorSet[900]!)); + return ContentAreaThemeData(decoration: decoration, decorationNoTabsArea: decorationNoTabsArea); } static TabbedViewMenuThemeData menuTheme({required MaterialColor colorSet}) { return TabbedViewMenuThemeData( - border: Border.all(width: 1, color: colorSet[900]!), + decoration: BoxDecoration( + border: Border.all(width: 1, color: colorSet[900]!), + color: colorSet[50]!, + ), margin: EdgeInsets.all(8), menuItemPadding: EdgeInsets.all(8), textStyle: TextStyle(color: colorSet[900]!, fontSize: 13), - color: colorSet[50]!, hoverColor: colorSet[200]!, dividerColor: colorSet[400]!, dividerThickness: 1); diff --git a/lib/src/theme/default_themes/mobile_theme.dart b/lib/src/theme/default_themes/mobile_theme.dart index b4a85b8..8886664 100644 --- a/lib/src/theme/default_themes/mobile_theme.dart +++ b/lib/src/theme/default_themes/mobile_theme.dart @@ -9,10 +9,7 @@ import 'package:tabbed_view/src/theme/tabs_area_theme_data.dart'; /// Predefined mobile theme builder. class MobileTheme { - static TabbedViewThemeData build( - {required MaterialColor colorSet, - required Color accentColor, - required double fontSize}) { + static TabbedViewThemeData build({required MaterialColor colorSet, required Color accentColor, required double fontSize}) { Color borderColor = colorSet[500]!; Color foregroundColor = colorSet[900]!; Color backgroundColor = colorSet[50]!; @@ -41,14 +38,8 @@ class MobileTheme { accentColor: accentColor, fontSize: fontSize, foregroundColor: foregroundColor), - contentArea: contentAreaTheme( - backgroundColor: backgroundColor, borderColor: borderColor), - menu: menuTheme( - hoverColor: menuHoverColor, - foregroundColor: foregroundColor, - borderColor: borderColor, - fontSize: fontSize, - backgroundColor: menuColor)); + contentArea: contentAreaTheme(backgroundColor: backgroundColor, borderColor: borderColor), + menu: menuTheme(hoverColor: menuHoverColor, foregroundColor: foregroundColor, borderColor: borderColor, fontSize: fontSize, backgroundColor: menuColor)); } static TabsAreaThemeData tabsAreaTheme( @@ -97,44 +88,28 @@ class MobileTheme { hoverButtonBackground: BoxDecoration(color: highlightedColor), buttonPadding: const EdgeInsets.all(2), decoration: BoxDecoration(border: border), - draggingDecoration: - BoxDecoration(border: Border.all(color: borderColor, width: 1)), - innerBottomBorder: - BorderSide(color: Colors.transparent, width: borderHeight), - highlightedStatus: TabStatusThemeData( - innerBottomBorder: - BorderSide(color: borderColor, width: borderHeight)), - selectedStatus: TabStatusThemeData( - innerBottomBorder: - BorderSide(color: accentColor, width: borderHeight))); + draggingDecoration: BoxDecoration(border: Border.all(color: borderColor, width: 1)), + innerBottomBorder: BorderSide(color: Colors.transparent, width: borderHeight), + highlightedStatus: TabStatusThemeData(innerBottomBorder: BorderSide(color: borderColor, width: borderHeight)), + selectedStatus: TabStatusThemeData(innerBottomBorder: BorderSide(color: accentColor, width: borderHeight))); } - static ContentAreaThemeData contentAreaTheme( - {required Color borderColor, required Color backgroundColor}) { + static ContentAreaThemeData contentAreaTheme({required Color borderColor, required Color backgroundColor}) { BorderSide borderSide = BorderSide(width: 1, color: borderColor); - BoxDecoration decoration = BoxDecoration( - color: backgroundColor, - border: - Border(bottom: borderSide, left: borderSide, right: borderSide)); - BoxDecoration decorationNoTabsArea = BoxDecoration( - color: backgroundColor, - border: Border.all(width: 1, color: borderColor)); - return ContentAreaThemeData( - decoration: decoration, decorationNoTabsArea: decorationNoTabsArea); + BoxDecoration decoration = BoxDecoration(color: backgroundColor, border: Border(bottom: borderSide, left: borderSide, right: borderSide)); + BoxDecoration decorationNoTabsArea = BoxDecoration(color: backgroundColor, border: Border.all(width: 1, color: borderColor)); + return ContentAreaThemeData(decoration: decoration, decorationNoTabsArea: decorationNoTabsArea); } - static TabbedViewMenuThemeData menuTheme( - {required Color backgroundColor, - required double fontSize, - required Color borderColor, - required Color foregroundColor, - required Color hoverColor}) { + static TabbedViewMenuThemeData menuTheme({required Color backgroundColor, required double fontSize, required Color borderColor, required Color foregroundColor, required Color hoverColor}) { return TabbedViewMenuThemeData( + decoration: BoxDecoration( + border: Border.all(width: 1, color: borderColor), + color: backgroundColor, + ), textStyle: TextStyle(fontSize: fontSize, color: foregroundColor), - border: Border.all(width: 1, color: borderColor), margin: EdgeInsets.all(8), menuItemPadding: EdgeInsets.all(8), - color: backgroundColor, hoverColor: hoverColor, dividerColor: borderColor, dividerThickness: 1); diff --git a/lib/src/theme/menu_theme_data.dart b/lib/src/theme/menu_theme_data.dart index 91728f8..a921623 100644 --- a/lib/src/theme/menu_theme_data.dart +++ b/lib/src/theme/menu_theme_data.dart @@ -2,21 +2,20 @@ import 'package:flutter/material.dart'; /// Theme for menu. class TabbedViewMenuThemeData { - TabbedViewMenuThemeData( - {this.padding, - this.margin, - this.menuItemPadding, - this.textStyle = const TextStyle(fontSize: 13), - this.border, - this.color, - this.blur = true, - this.ellipsisOverflowText = false, - double dividerThickness = 0, - double maxWidth = 200, - this.dividerColor, - this.hoverColor}) - : this._dividerThickness = dividerThickness >= 0 ? dividerThickness : 0, - this._maxWidth = maxWidth >= 0 ? maxWidth : 0; + TabbedViewMenuThemeData({ + this.padding, + this.margin, + this.menuItemPadding, + this.textStyle = const TextStyle(fontSize: 13), + this.decoration, + this.blur = true, + this.ellipsisOverflowText = false, + double dividerThickness = 0, + double maxWidth = 200, + this.dividerColor, + this.hoverColor, + }) : _dividerThickness = dividerThickness >= 0 ? dividerThickness : 0, + _maxWidth = maxWidth >= 0 ? maxWidth : 0; EdgeInsetsGeometry? margin; @@ -36,10 +35,6 @@ class TabbedViewMenuThemeData { TextStyle? textStyle; - Border? border; - - Color? color; - Color? hoverColor; /// Indicates whether to apply a blur effect on the content. @@ -47,17 +42,23 @@ class TabbedViewMenuThemeData { Color? dividerColor; + BoxDecoration? decoration; + /// Use an ellipsis to indicate that the text has overflowed. bool ellipsisOverflowText; double _dividerThickness; + double get dividerThickness => _dividerThickness; + set dividerThickness(double value) { _dividerThickness = value >= 0 ? value : 0; } double _maxWidth; + double get maxWidth => _maxWidth; + set maxWidth(double value) { _maxWidth = value >= 0 ? value : 0; } @@ -71,8 +72,7 @@ class TabbedViewMenuThemeData { padding == other.padding && menuItemPadding == other.menuItemPadding && textStyle == other.textStyle && - border == other.border && - color == other.color && + decoration == other.decoration && hoverColor == other.hoverColor && blur == other.blur && dividerColor == other.dividerColor && @@ -86,8 +86,7 @@ class TabbedViewMenuThemeData { padding.hashCode ^ menuItemPadding.hashCode ^ textStyle.hashCode ^ - border.hashCode ^ - color.hashCode ^ + decoration.hashCode ^ hoverColor.hashCode ^ blur.hashCode ^ dividerColor.hashCode ^ diff --git a/lib/src/theme/tab_theme_data.dart b/lib/src/theme/tab_theme_data.dart index d7502ca..10455c4 100644 --- a/lib/src/theme/tab_theme_data.dart +++ b/lib/src/theme/tab_theme_data.dart @@ -8,43 +8,39 @@ import 'package:tabbed_view/src/theme/vertical_alignment.dart'; /// Theme for tab. class TabThemeData { - TabThemeData( - {IconProvider? closeIcon, - this.normalButtonColor = Colors.black, - this.hoverButtonColor = Colors.black, - this.disabledButtonColor = Colors.black12, - this.normalButtonBackground, - this.hoverButtonBackground, - this.disabledButtonBackground, - double buttonIconSize = TabbedViewThemeConstants.defaultIconSize, - this.verticalAlignment = VerticalAlignment.center, - double buttonsOffset = 0, - this.buttonPadding, - double buttonsGap = 0, - this.decoration, - this.draggingDecoration, - this.draggingOpacity = 0.3, - this.innerBottomBorder, - this.innerTopBorder, - this.textStyle = const TextStyle(fontSize: 13), - this.padding, - this.paddingWithoutButton, - this.margin, - TabStatusThemeData? selectedStatus, - TabStatusThemeData? highlightedStatus, - TabStatusThemeData? disabledStatus}) - : this._buttonsOffset = buttonsOffset >= 0 ? buttonsOffset : 0, + TabThemeData({ + IconProvider? closeIcon, + this.normalButtonColor = Colors.black, + this.hoverButtonColor = Colors.black, + this.disabledButtonColor = Colors.black12, + this.normalButtonBackground, + this.hoverButtonBackground, + this.disabledButtonBackground, + double buttonIconSize = TabbedViewThemeConstants.defaultIconSize, + this.verticalAlignment = VerticalAlignment.center, + double buttonsOffset = 0, + this.buttonPadding, + double buttonsGap = 0, + this.decoration, + this.draggingDecoration, + this.draggingOpacity = 0.3, + this.innerBottomBorder, + this.innerTopBorder, + this.textStyle = const TextStyle(fontSize: 13), + this.padding, + this.paddingWithoutButton, + this.margin, + TabStatusThemeData? selectedStatus, + TabStatusThemeData? highlightedStatus, + TabStatusThemeData? disabledStatus, + this.maxWidth, + this.minWidth, + }) : this._buttonsOffset = buttonsOffset >= 0 ? buttonsOffset : 0, this._buttonsGap = buttonsGap >= 0 ? buttonsGap : 0, - this.buttonIconSize = - TabbedViewThemeConstants.normalize(buttonIconSize), - this.closeIcon = closeIcon == null - ? IconProvider.path(TabbedViewIcons.close) - : closeIcon, - this.selectedStatus = - selectedStatus != null ? selectedStatus : TabStatusThemeData(), - this.highlightedStatus = highlightedStatus != null - ? highlightedStatus - : TabStatusThemeData(); + this.buttonIconSize = TabbedViewThemeConstants.normalize(buttonIconSize), + this.closeIcon = closeIcon == null ? IconProvider.path(TabbedViewIcons.close) : closeIcon, + this.selectedStatus = selectedStatus != null ? selectedStatus : TabStatusThemeData(), + this.highlightedStatus = highlightedStatus != null ? highlightedStatus : TabStatusThemeData(); /// Empty space to inscribe inside the [decoration]. The tab child, if any, is /// placed inside this padding. @@ -88,6 +84,10 @@ class TabThemeData { TabStatusThemeData selectedStatus; TabStatusThemeData highlightedStatus; + /// tab max/min width + double? maxWidth; + double? minWidth; + double _buttonsOffset; double get buttonsOffset => _buttonsOffset; @@ -145,7 +145,9 @@ class TabThemeData { highlightedStatus == other.highlightedStatus && _buttonsOffset == other._buttonsOffset && buttonPadding == other.buttonPadding && - _buttonsGap == other._buttonsGap; + _buttonsGap == other._buttonsGap && + maxWidth == other.maxWidth && + minWidth == other.minWidth; @override int get hashCode => @@ -171,5 +173,7 @@ class TabThemeData { highlightedStatus.hashCode ^ _buttonsOffset.hashCode ^ buttonPadding.hashCode ^ - _buttonsGap.hashCode; + _buttonsGap.hashCode ^ + maxWidth.hashCode ^ + minWidth.hashCode; } diff --git a/lib/src/theme/tabs_area_theme_data.dart b/lib/src/theme/tabs_area_theme_data.dart index b684aa0..62b9204 100644 --- a/lib/src/theme/tabs_area_theme_data.dart +++ b/lib/src/theme/tabs_area_theme_data.dart @@ -3,6 +3,7 @@ import 'package:tabbed_view/src/icon_provider.dart'; import 'package:tabbed_view/src/tabbed_view_icons.dart'; import 'package:tabbed_view/src/theme/equal_heights.dart'; import 'package:tabbed_view/src/theme/tabbed_view_theme_constants.dart'; +import 'package:tabbed_view/tabbed_view.dart'; ///Theme for tabs and buttons area. class TabsAreaThemeData { @@ -32,11 +33,8 @@ class TabsAreaThemeData { : this._minimalFinalGap = minimalFinalGap >= 0 ? minimalFinalGap : 0, this._buttonsOffset = buttonsOffset >= 0 ? buttonsOffset : 0, this._buttonsGap = buttonsGap >= 0 ? buttonsGap : 0, - this.buttonIconSize = - TabbedViewThemeConstants.normalize(buttonIconSize), - this.menuIcon = menuIcon == null - ? IconProvider.path(TabbedViewIcons.menu) - : menuIcon; + this.buttonIconSize = TabbedViewThemeConstants.normalize(buttonIconSize), + this.menuIcon = menuIcon == null ? IconProvider.path(TabbedViewIcons.menu) : menuIcon; bool visible; @@ -77,6 +75,12 @@ class TabsAreaThemeData { /// Icon for the hidden tabs menu. IconProvider menuIcon; + /// custom drop over painter + CustomPainter? dropOverPainter; + + /// Icon for the add tab + TabButton? addButton; + double _buttonsGap; double get buttonsGap => _buttonsGap; @@ -113,7 +117,9 @@ class TabsAreaThemeData { menuIcon == other.menuIcon && _buttonsGap == other._buttonsGap && _buttonsOffset == other._buttonsOffset && - buttonPadding == other.buttonPadding; + buttonPadding == other.buttonPadding && + dropOverPainter == other.dropOverPainter && + addButton == other.addButton; @override int get hashCode => @@ -138,6 +144,9 @@ class TabsAreaThemeData { menuIcon.hashCode ^ _buttonsGap.hashCode ^ _buttonsOffset.hashCode ^ + buttonPadding.hashCode ^ + dropOverPainter.hashCode ^ + addButton.hashCode ^ buttonPadding.hashCode; double get buttonsOffset => _buttonsOffset;