Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MacosTabView & MacosTab #206

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf3e261
Initial implementation of MacosTabView
Andrewngabriel Apr 21, 2022
77ef04f
Add example page showcasing MacosTabView widget
Andrewngabriel Apr 21, 2022
2b3dc04
Merge branch 'dev' into feature/tab-view
Andrewngabriel Apr 24, 2022
8a2aeb4
Add MacosTab
Andrewngabriel Apr 24, 2022
ccdc9ad
Merge branch 'dev' into feature/tab-view
GroovinChip Apr 29, 2022
82f5133
Merge branch 'dev' into feature/tab-view
Andrewngabriel Apr 29, 2022
68a418b
Merge remote-tracking branch 'origin/feature/tab-view' into feature/t…
Andrewngabriel Apr 29, 2022
a0da0ba
Update color styles in MacosTabView to reflect dark/light mode
Andrewngabriel Apr 29, 2022
68260a8
Implement active state for MacosTab
Andrewngabriel Apr 30, 2022
47c6f02
dart lint fixes
Andrewngabriel Apr 30, 2022
8758880
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
a927c4f
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
914a753
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
1e0a15e
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
999cab4
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
cc764cb
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
ddb9178
Update lib/src/layout/tab.dart
GroovinChip Apr 30, 2022
2ff5c7e
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
bc49283
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
9c7a37f
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
de4f3c3
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
f72d5ac
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
83c5d64
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
957caf3
Update lib/src/layout/tab_view.dart
GroovinChip Apr 30, 2022
e37f250
chore: fixes for MacosTab & MacosTabView
GroovinChip Apr 30, 2022
7fa891c
Merge branch 'dev' into feature/tab-view
GroovinChip May 4, 2022
1d8f486
Merge branch 'dev' into feature/tab-view
GroovinChip May 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions example/lib/main.dart
Expand Up @@ -4,6 +4,7 @@ import 'package:example/pages/dialogs_page.dart';
import 'package:example/pages/fields.dart';
import 'package:example/pages/indicators.dart';
import 'package:example/pages/selectors_page.dart';
import 'package:example/pages/tab_view.dart';
import 'package:flutter/cupertino.dart';
import 'package:macos_ui/macos_ui.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -64,6 +65,7 @@ class _DemoState extends State<Demo> {
),
const DialogsPage(),
const SelectorsPage(),
const TabViewPage(),
];

Color textLuminance(Color backgroundColor) {
Expand Down Expand Up @@ -128,6 +130,10 @@ class _DemoState extends State<Demo> {
leading: MacosIcon(CupertinoIcons.calendar),
label: Text('Selectors'),
),
SidebarItem(
leading: MacosIcon(CupertinoIcons.square_stack_3d_up),
label: Text('Tab View'),
),
],
);
},
Expand Down
118 changes: 118 additions & 0 deletions example/lib/pages/tab_view.dart
@@ -0,0 +1,118 @@
import 'package:flutter/widgets.dart';
import 'package:macos_ui/macos_ui.dart';

class TabViewPage extends StatefulWidget {
const TabViewPage({Key? key}) : super(key: key);

@override
State<TabViewPage> createState() => _TabViewPageState();
}

class _TabViewPageState extends State<TabViewPage> {
int activeIndex = 0;
MacosTabPosition positionSelected = MacosTabPosition.top;
Widget content = Container();

void updatePosition(MacosTabPosition? pos) {
return setState(() => positionSelected = pos ?? MacosTabPosition.top);
}

Widget _radioButton(MacosTabPosition pos) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MacosRadioButton<MacosTabPosition>(
groupValue: positionSelected,
value: pos,
onChanged: updatePosition,
),
const SizedBox(width: 10),
Text(pos.name),
],
),
);
}

@override
Widget build(BuildContext context) {
return MacosScaffold(
titleBar: const TitleBar(
title: Text('macOS Tab View'),
),
children: [
ContentArea(
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.all(20),
child: Center(
child: Column(
children: [
const Text('Tab View Controls Position'),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_radioButton(MacosTabPosition.top),
_radioButton(MacosTabPosition.bottom),
_radioButton(MacosTabPosition.left),
_radioButton(MacosTabPosition.right),
],
),
const SizedBox(height: 30),
MacosTabView(
width: 500,
height: 400,
position: positionSelected,
body: content,
tabs: [
MacosTab(
label: 'Sound Effects',
active: activeIndex == 0,
onTap: () {
setState(() {
activeIndex = 0;
content = const Center(
child: Text('Sound Effects'),
);
});
},
),
MacosTab(
label: 'Input',
active: activeIndex == 1,
onTap: () {
setState(() {
activeIndex = 1;
content = const Center(
child: Text('Input'),
);
});
},
),
MacosTab(
label: 'Output',
active: activeIndex == 2,
onTap: () {
setState(() {
activeIndex = 2;
content = const Center(
child: Text('Output'),
);
});
},
),
],
),
],
),
),
);
},
),
],
);
}
}
2 changes: 2 additions & 0 deletions lib/macos_ui.dart
Expand Up @@ -35,6 +35,8 @@ export 'src/layout/resizable_pane.dart';
export 'src/layout/scaffold.dart';
export 'src/layout/sidebar.dart';
export 'src/layout/sidebar_item.dart';
export 'src/layout/tab.dart';
export 'src/layout/tab_view.dart';
export 'src/layout/title_bar.dart';
export 'src/layout/window.dart';
export 'src/macos_app.dart';
Expand Down
48 changes: 48 additions & 0 deletions lib/src/layout/tab.dart
@@ -0,0 +1,48 @@
import '../../macos_ui.dart';
import '../library.dart';

/// {template macosTab}
/// An item in a [MacosTabView].
/// {endtemplate}
class MacosTab extends StatelessWidget {
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved
/// {macro macosTab}
const MacosTab({
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved
Key? key,
required this.label,
required this.onTap,
this.active = false,
}) : super(key: key);

/// Describes the content of the [MacosTabView] pane it represents.
final String label;
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved
/// The action to perform when this widget is clicked.
///
/// It is expected that this action will change the current [MacosTabView] content.
final VoidCallback onTap;
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved
/// Whether this widget is the active tab.
///
/// Defaults to `false`.
final bool active;
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved

@override
Widget build(BuildContext context) {
final brightness = MacosTheme.brightnessOf(context);

return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.elliptical(6, 6)),
color: active
? brightness.resolve(
MacosColors.white,
const Color.fromRGBO(95, 96, 97, 1.0),
)
: MacosColors.transparent,
),
child: Text(label),
),
);
}
}
167 changes: 167 additions & 0 deletions lib/src/layout/tab_view.dart
@@ -0,0 +1,167 @@
import 'package:macos_ui/macos_ui.dart';

import '../library.dart';

const _kTabViewRadius = BorderRadius.all(Radius.elliptical(5, 5));

/// Specifies layout position for [MacosTab] options inside [MacosTabView]
enum MacosTabPosition {
left,
right,
top,
bottom,
}

/// {template macosTabView}
/// Presents multiple mutually exclusive panes of content in the same area.
///
/// Includes a tabbed control (which is similar in appearance to a
/// [segmented control](https://developer.apple.com/design/human-interface-guidelines/macos/selectors/segmented-controls/))
/// and a content area.
///
/// Each segment of a tabbed control is known as a tab, and clicking a tab displays its corresponding pane
/// in the content area. Although the amount of content can vary from pane to pane, switching tabs doesn’t
/// change the overall size of the tab view or its parent window.
///
/// You can position the tabbed control on any side of the content area—top, bottom, left, or right.
///
/// **Use a tab view to present closely related peer areas of content.** The typical appearance of a tab view
/// provides a strong visual indication of enclosure. People expect each tab to display content that is in
/// some way similar or related to the content in the other tabs.
///
/// **Make sure the controls within a pane only affect content in the same pane.** Panes are mutually
/// exclusive and should be fully self-contained.
///
/// In general, inset a tab view by leaving a margin of window-body area on all sides of a tab view.
/// This layout looks clean and leaves room for additional controls that can affect the window itself
/// (or other tabs). For example, the lock button in the macOD Date & Time preferences is outside of
/// the tab view because it applies to all tabs.
///
/// **Provide a label for each tab that describes the contents of its pane.** A good label helps the
/// user predict the contents of a pane before clicking its tab. In general, use nouns or very short
/// noun phrases for tab labels. A verb or short verb phrase may make sense in some contexts.
/// Tab labels should use title-style capitalization.
/// {endTemplate}
class MacosTabView extends StatelessWidget {
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved
/// {macro macosTabView}
const MacosTabView({
GroovinChip marked this conversation as resolved.
Show resolved Hide resolved
Key? key,
required this.tabs,
required this.body,
this.position = MacosTabPosition.top,
this.width,
this.height,
this.constraints,
}) : assert(tabs.length > 0),
super(key: key);

/// Specifies the tabs to display at any given edge of the tab view content.
final List<Widget> tabs;

/// Specifies the position of the tab view controls.
///
/// Defaults to [MacosTabPosition.top].
final MacosTabPosition position;

/// The content to display inside of the tab view.
final Widget body;

final double? width;
final double? height;
final BoxConstraints? constraints;

int get _tabRotation {
switch (position) {
case MacosTabPosition.left:
return 3;
case MacosTabPosition.right:
return 1;
case MacosTabPosition.top:
return 0;
case MacosTabPosition.bottom:
return 0;
}
}

@override
Widget build(BuildContext context) {
assert(debugCheckHasMacosTheme(context));
final brightness = MacosTheme.brightnessOf(context);

final outerBorderColor = brightness.resolve(
const Color.fromRGBO(218, 219, 219, 1.0),
const Color.fromRGBO(63, 64, 66, 1.0),
);

return Stack(
alignment: Alignment.center,
children: [
Container(
width: width ?? double.infinity,
height: height ?? double.infinity,
constraints: constraints,
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: brightness.resolve(
const Color.fromRGBO(230, 231, 235, 1.0),
const Color.fromRGBO(36, 37, 38, 1.0),
),
border: Border.all(color: outerBorderColor, width: 1),
borderRadius: _kTabViewRadius,
),
child: body,
),
Positioned(
top: position == MacosTabPosition.top ? 0 : null,
bottom: position == MacosTabPosition.bottom ? 0 : null,
left: position == MacosTabPosition.left ? 0 : null,
right: position == MacosTabPosition.right ? 0 : null,
child: RotatedBox(
quarterTurns: _tabRotation,
child: ClipRRect(
borderRadius: _kTabViewRadius,
child: Container(
decoration: BoxDecoration(
color: brightness.resolve(
const Color.fromRGBO(226, 227, 231, 1.0),
const Color.fromRGBO(41, 42, 43, 1.0),
),
border: Border.all(
color: brightness.resolve(
const Color.fromRGBO(216, 216, 219, 1.0),
const Color.fromRGBO(87, 89, 90, 1.0),
),
),
),
child: IntrinsicHeight(
child: Row(
children: tabs.map((t) {
Row row = Row(children: [t]);
bool last = tabs.indexOf(t) == tabs.length - 1;
if (!last) {
row.children.add(
VerticalDivider(
color: brightness.resolve(
const Color.fromRGBO(202, 203, 206, 1.0),
const Color.fromRGBO(34, 36, 37, 1.0),
),
width: 0,
indent: 5,
endIndent: 5,
),
);
}

return row;
}).toList(growable: false),
),
),
),
),
),
),
],
);
}
}