plat 0.1.1
plat: ^0.1.1 copied to clipboard
Composable split panes, tab groups, and controller-driven drag-and-drop workspaces for Flutter.
Plat
Composable pane layouts for Flutter.
Split panes, tab groups, drag-and-drop, and controller-driven workspaces.
About · Features · Getting started · Layout · Customization
About #
Plat is a highly customizable and flexible package for building workspace layouts, from simple split panes to complex IDE-style editors. It provides tab groups, resizable splits, drag-and-drop, snapshots, undoable commands, content builders, and themeable chrome.
Features #
- Split workspaces: Compose rows, columns, slots, leaves, and tab groups.
- Tab workflows: Reorder, drag, pin, lock, preview, close, and move tabs.
- Drag-and-drop layouts: Move tabs or panes within one view or across views.
- Resizable panes: Combine fixed, fractional, auto, minimum, and maximum extents.
- Controller commands: Focus, close, insert, split, hide, maximize, undo, and redo.
- Stable snapshots: Read layout state by id for rendering and commands.
- Drop policies: Accept or reject drops by source controller, target, and zone.
- Composable styling: Theme dividers, drop hints, tab bars, and tab chips.
- Keyboard actions: Built-in shortcuts for focus and layout operations.
Getting started #
Add plat to your app:
flutter pub add plat
Or add it manually:
dependencies:
plat: ^0.1.1
Then import the package:
import 'package:plat/plat.dart';
Layout #
A Plat tree describes the workspace shape:
Plat: a row, column, tab group, slot, or leaf.Plat.row/Plat.column: split children horizontally or vertically.Plat.tabs: group tabs and render the active tab's child.PlatTab: tab metadata such as title, pinned, locked, and preview state.Plat.leaf: a content endpoint rendered by yourleafBuilder.Plat.slot: a region for empty states, stable ids, and scoped maximize.id: stable string identity for builders, focus, drops, and commands.PlatSize/PlatExtent: fixed, fractional, auto, and resizable space.
final controller = PlatController(
initialPlat: .row(
children: [
.tabs(
[
.leaf(id: 'main', title: 'main.dart'),
.leaf(id: 'readme', title: 'README.md'),
],
id: 'editors',
),
const .slot(
id: 'inspector',
size: .fixed(.pixel(280)),
child: .leaf(id: 'inspector-pane', title: 'Inspector'),
),
],
),
);
Rendering #
PlatView renders the controller tree. Build each leaf with your widgets;
Plat renders chrome, tabs, dividers, drops, shortcuts, and focus state. Switch
on leaf.id, leaf.title, or leaf.data when panes need different content.
PlatView(
controller: controller,
leafBuilder: (context, leaf) => switch (leaf.id) {
'inspector-pane' => InspectorPane(leaf: leaf),
_ => EditorPane(leaf: leaf),
},
);
Controller #
Use PlatController to change the workspace. Structural changes support undo
and redo.
controller.insertTab(
tabGroupId: 'editors',
tab: .leaf(id: 'settings', title: 'Settings'),
);
controller.split(
targetId: 'main',
side: .right,
sibling: .tabs([
.leaf(id: 'preview', title: 'Preview'),
]),
);
controller.close('readme');
controller.undo();
Customization #
Theme #
PlatTheme styles the layout chrome for the PlatViews below it. Use it for
dividers, drop feedback, tabs, and animation timing.
PlatTheme(
data: const PlatThemeData(
divider: PlatDividerTheme(thickness: 2, hitSlop: 6),
dropHint: PlatDropHintTheme(duration: Duration(milliseconds: 160)),
tabBar: PlatTabBarTheme(
size: 36,
fit: .expand,
chipMinWidth: 72,
chipMaxWidth: 220,
labelPadding: .symmetric(horizontal: 10),
),
),
child: PlatView(
controller: controller,
leafBuilder: (context, leaf) => switch (leaf.id) {
'inspector-pane' => InspectorPane(leaf: leaf),
_ => EditorPane(leaf: leaf),
},
),
);
Tab chrome #
For a custom tab group, return a PlatTabBar from PlatView.tabBar. Reuse the
default chip and replace the slots that need custom content.
PlatView(
controller: controller,
tabBar: (context, tabs) => PlatTabBar(
tabBuilder: (context, tab) => PlatTabChip(
leading: const Icon(Icons.description, size: 14),
label: Text(tab.snapshot.title),
trailing: const PlatTabCloseButton(),
),
),
leafBuilder: (context, leaf) => switch (leaf.id) {
'inspector-pane' => InspectorPane(leaf: leaf),
_ => EditorPane(leaf: leaf),
},
);
Multiple views #
One PlatView can render deeply nested workspaces. Use multiple views when
separate regions or controllers need to exchange tabs, filter drops, or
preserve leaf state during handoff.
Widget buildPane(BuildContext context, LeafSnapshot leaf) {
return switch (leaf.id) {
'inspector-pane' => InspectorPane(leaf: leaf),
_ => EditorPane(leaf: leaf),
};
}
PlatScope(
child: Row(
children: [
Expanded(
child: PlatView(
controller: mainController,
leafBuilder: buildPane,
),
),
Expanded(
child: PlatView(
controller: sideController,
leafBuilder: buildPane,
autofocus: false,
dropPolicy: (attempt) => attempt.sourceController == mainController,
),
),
],
),
);