Skip to content

Commit

Permalink
Added window component
Browse files Browse the repository at this point in the history
  • Loading branch information
sunarya-thito committed Jan 23, 2025
1 parent 2e66228 commit cbce8ed
Show file tree
Hide file tree
Showing 9 changed files with 2,289 additions and 1 deletion.
21 changes: 21 additions & 0 deletions docs/lib/debug.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';

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

@override
State<RebuildCounter> createState() => _RebuildCounterState();
}

class _RebuildCounterState extends State<RebuildCounter> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.primaries[hashCode % Colors.primaries.length],
child: Center(
child: Text('Rebuild count: ${counter++}'),
),
);
}
}
6 changes: 6 additions & 0 deletions docs/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import 'package:docs/pages/docs/components/toggle_example.dart';
import 'package:docs/pages/docs/components/tooltip_example.dart';
import 'package:docs/pages/docs/components/tracker_example.dart';
import 'package:docs/pages/docs/components/tree_example.dart';
import 'package:docs/pages/docs/components/window_example.dart';
import 'package:docs/pages/docs/components_page.dart';
import 'package:docs/pages/docs/icons_page.dart';
import 'package:docs/pages/docs/installation_page.dart';
Expand Down Expand Up @@ -663,6 +664,11 @@ class MyAppState extends State<MyApp> {
path: 'tab_pane',
builder: (context, state) => const TabPaneExample(),
name: 'tab_pane',
),
GoRoute(
path: 'window',
builder: (context, state) => const WindowExample(),
name: 'window',
)
]),
]);
Expand Down
55 changes: 55 additions & 0 deletions docs/lib/pages/docs/components/window/window_example_1.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:docs/debug.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

class WindowExample1 extends StatefulWidget {
const WindowExample1({super.key});

@override
State<WindowExample1> createState() => _WindowExample1State();
}

class _WindowExample1State extends State<WindowExample1> {
final GlobalKey<WindowNavigatorHandle> navigatorKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
OutlinedContainer(
height: 600, // for example purpose
child: WindowNavigator(
key: navigatorKey,
child: const Center(
child: Text('Desktop'),
),
initialWindows: [
Window(
bounds: Rect.fromLTWH(0, 0, 200, 200),
title: Text('Window 1'),
content: const RebuildCounter(),
),
Window(
bounds: Rect.fromLTWH(200, 0, 200, 200),
title: Text('Window 2'),
content: const RebuildCounter(),
),
],
),
),
PrimaryButton(
child: const Text('Add Window'),
onPressed: () {
navigatorKey.currentState?.pushWindow(
Window(
bounds: Rect.fromLTWH(0, 0, 200, 200),
title: Text(
'Window ${navigatorKey.currentState!.windows.length + 1}'),
content: const RebuildCounter(),
),
);
},
)
],
);
}
}
25 changes: 25 additions & 0 deletions docs/lib/pages/docs/components/window_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:docs/pages/docs/component_page.dart';
import 'package:docs/pages/docs/components/window/window_example_1.dart';
import 'package:docs/pages/widget_usage_example.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

class WindowExample extends StatelessWidget {
const WindowExample({super.key});

@override
Widget build(BuildContext context) {
return const ComponentPage(
name: 'window',
description:
'A window manager that allows you to create and manage windows.',
displayName: 'Window',
children: [
WidgetUsageExample(
title: 'Window Example',
path: 'lib/pages/docs/components/window/window_example_1.dart',
child: WindowExample1(),
),
],
);
}
}
2 changes: 1 addition & 1 deletion docs/lib/pages/docs_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class DocsPageState extends State<DocsPage> {
ShadcnDocsPage('Sheet', 'sheet'),
ShadcnDocsPage('Tooltip', 'tooltip'),
// TODO: window as in like a window in desktop
ShadcnDocsPage('Window', 'window', ShadcnFeatureTag.workInProgress),
ShadcnDocsPage('Window', 'window', ShadcnFeatureTag.experimental),
],
),

Expand Down
1 change: 1 addition & 0 deletions lib/shadcn_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export 'src/components/layout/steps.dart';
export 'src/components/layout/table.dart';
export 'src/components/layout/timeline.dart';
export 'src/components/layout/tree.dart';
export 'src/components/layout/window.dart';
export 'src/components/locale/shadcn_localizations.dart';
export 'src/components/menu/context_menu.dart';
export 'src/components/menu/dropdown_menu.dart';
Expand Down
189 changes: 189 additions & 0 deletions lib/src/components/layout/group.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import 'package:flutter/rendering.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

class GroupWidget extends MultiChildRenderObjectWidget {
GroupWidget({
Key? key,
List<Widget> children = const <Widget>[],
}) : super(key: key, children: children);

@override
RenderObject createRenderObject(BuildContext context) {
return RenderGroup();
}

@override
void updateRenderObject(BuildContext context, RenderGroup renderObject) {}
}

class GroupParentData extends ContainerBoxParentData<RenderBox> {
double? top;
double? left;
double? right;
double? bottom;
double? width;
double? height;
}

class RenderGroup extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, GroupParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, GroupParentData> {
RenderGroup({
List<RenderBox>? children,
}) {
addAll(children);
}

@override
void setupParentData(RenderBox child) {
if (child.parentData is! GroupParentData) {
child.parentData = GroupParentData();
}
}

@override
void performLayout() {
var child = firstChild;
while (child != null) {
final childParentData = child.parentData as GroupParentData;

double? top = childParentData.top;
double? left = childParentData.left;
double? right = childParentData.right;
double? bottom = childParentData.bottom;
double? width = childParentData.width;
double? height = childParentData.height;

// do positioned layouting
double offsetX = 0;
double offsetY = 0;
double childWidth = 0;
double childHeight = 0;
if (top != null && bottom != null) {
childHeight = bottom - top;
} else {
// either top or bottom is null
if (top != null) {
offsetY = top;
} else if (bottom != null) {
offsetY = constraints.maxHeight - bottom;
}
if (height != null) {
childHeight = height;
} else {
childHeight = constraints.maxHeight;
}
}
if (left != null && right != null) {
childWidth = right - left;
} else {
// either left or right is null
if (left != null) {
offsetX = left;
} else if (right != null) {
offsetX = constraints.maxWidth - right;
}
if (width != null) {
childWidth = width;
} else {
childWidth = constraints.maxWidth;
}
}
child.layout(
BoxConstraints.tightFor(width: childWidth, height: childHeight));
childParentData.offset = Offset(offsetX, offsetY);
child = childParentData.nextSibling;
}
size = constraints.biggest;
}

@override
void paint(PaintingContext context, Offset offset) {
var child = firstChild;
while (child != null) {
final childParentData = child.parentData as GroupParentData;
context.paintChild(child, offset + childParentData.offset);
child = childParentData.nextSibling;
}
}

@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
return hitTestChildren(result, position: position);
}

@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
var child = lastChild;
while (child != null) {
final childParentData = child.parentData as GroupParentData;
if (child.hitTest(result, position: position - childParentData.offset)) {
return true;
}
child = childParentData.previousSibling;
}
return false;
}
}

class GroupPositioned extends ParentDataWidget<GroupParentData> {
const GroupPositioned({
Key? key,
this.top,
this.left,
this.right,
this.bottom,
this.width,
this.height,
required Widget child,
}) : super(key: key, child: child);

final double? top;
final double? left;
final double? right;
final double? bottom;
final double? width;
final double? height;

@override
void applyParentData(RenderObject renderObject) {
final parentData = renderObject.parentData as GroupParentData;
bool needsLayout = false;

if (parentData.top != top) {
parentData.top = top;
needsLayout = true;
}
if (parentData.left != left) {
parentData.left = left;
needsLayout = true;
}
if (parentData.right != right) {
parentData.right = right;
needsLayout = true;
}
if (parentData.bottom != bottom) {
parentData.bottom = bottom;
needsLayout = true;
}
if (parentData.width != width) {
parentData.width = width;
needsLayout = true;
}
if (parentData.height != height) {
parentData.height = height;
needsLayout = true;
}

if (needsLayout) {
final targetParent = renderObject.parent;
if (targetParent is RenderObject) {
targetParent.markNeedsLayout();
}
}
}

@override
Type get debugTypicalAncestorWidgetClass => GroupWidget;
}
Loading

0 comments on commit cbce8ed

Please sign in to comment.