panes 1.0.0+1 copy "panes: ^1.0.0+1" to clipboard
panes: ^1.0.0+1 copied to clipboard

A powerful Flutter package for creating complex, resizable, multi-pane layouts. Ideal for building IDE-like interfaces, advanced dashboards, and desktop applications.

example/lib/main.dart

/// Panes Package - Complete API Demonstration
///
/// This example showcases ALL available APIs of the panes package:
///
/// ## IdeLayout & IdeController (High-Level API)
/// - `IdeController` - Manages IDE-like layout with left, right, center, bottom panes
/// - `leftSize`, `rightSize`, `bottomSize` - Initial panel sizing with PaneSize
/// - `toggleLeft()`, `toggleRight()`, `toggleBottom()` - Panel visibility toggles
/// - `save()`, `load()` - Serialize/deserialize layout state
/// - `animationDuration` - Animation timing for panel transitions
///
/// ## MultiPane (Low-Level API)
/// - Core resizable pane widget for custom layouts
/// - `direction` - Axis.horizontal or Axis.vertical
/// - `controller` - PaneController for managing state
/// - `paneBuilder` - Builder for each pane by ID
/// - `animationDuration`, `animationCurve` - Animation configuration
///
/// ## PaneController APIs (via IdeController.rootController & centerController)
/// - `show(id)`, `hide(id)`, `toggle(id)` - Panel visibility control
/// - `isVisible(id)` - Check panel visibility
/// - `maximize(id)`, `restore()`, `toggleMaximize(id)` - Maximize/restore panels
/// - `isMaximized`, `maximizedPaneId` - Maximization state
/// - `updateSize(id, size)` - Programmatic size changes
/// - `resetSize(id)`, `resetAll()` - Reset sizes to initial values
/// - `getPixelSize(id)`, `getFractionalSize(id)` - Query current sizes
/// - `entries` - Access pane configuration list
/// - `dispose()` - Clean up resources (via IdeController.dispose())
///
/// ## PaneSize
/// - `PaneSize.pixel(value)` - Fixed pixel sizing
/// - `PaneSize.fraction(value)` - Flexible/fractional sizing (like Expanded flex)
///
/// ## PaneEntry Configuration
/// - `id`, `initialSize` - Required pane identifiers and sizing
/// - `visible` - Initial visibility state
/// - `minSize`, `maxSize` - Size constraints
/// - `autoHide`, `autoHideThreshold` - Auto-collapse when resized too small
/// - `copyWith()` - Create modified copies
///
/// ## PaneThemeData (full configuration)
/// - `resizerColor`, `resizerHoverColor`, `resizerFocusedColor` - Resizer states
/// - `resizerThickness`, `resizerHitTestThickness` - Resizer dimensions
/// - `tabHeaderColor`, `tabBackground`, `tabSelectedBackground` - Tab colors
/// - `tabLabelColor`, `tabSelectedLabelColor` - Tab text colors
/// - `copyWith()`, `lerp()` - Theme manipulation
/// - Can be used as ThemeExtension or via PaneTheme inherited widget
///
/// ## TabbedPane
/// - `icons`, `labels` - Tab configuration
/// - `selectedIndex`, `onTabSelected` - Tab selection handling
/// - `tabBuilder` - Content builder per tab index
/// - `actions` - Header action widgets (buttons, etc.)
///
/// ## Interactive Features Demonstrated
/// - Drag resizers to resize panels (mouse & keyboard accessibility)
/// - Double-tap resizers to reset sizes to initial values
/// - Resize beyond threshold triggers auto-hide (left panel has autoHide enabled)
/// - Tab key to focus resizers, arrow keys to resize
/// - Title bar buttons for save/load, reset, and maximize features
/// - Panel headers with maximize and close buttons
library;

import 'package:flutter/material.dart';
import 'package:panes/panes.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Panes Example',
      theme: ThemeData.dark(useMaterial3: true).copyWith(
        scaffoldBackgroundColor: const Color(0xFF181818),
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFF181818),
          foregroundColor: Color(0xFFE8E8E8),
          elevation: 0,
        ),
      ),
      home: const IdeExample(),
    );
  }
}

// JetBrains Fleet-inspired color palette (Islands UI)
class IdeColors {
  // Base backgrounds - Fleet uses darker, more neutral tones
  static const background = Color(0xFF181818);
  static const surface = Color(0xFF1E1E1E);
  static const panel = Color(0xFF232324);
  static const panelBorder = Color(0xFF2E2E30);
  static const sidebar = Color(0xFF1E1E1E);
  static const activityBar = Color(0xFF181818);
  static const activityBarActive = Color(0xFF6C6CFF);
  static const tabBar = Color(0xFF232324);
  static const tabActive = Color(0xFF2A2A2C);
  static const tabInactive = Color(0xFF232324);
  static const border = Color(0xFF2E2E30);
  static const statusBar = Color(0xFF181818);
  static const text = Color(0xFFE8E8E8);
  static const textMuted = Color(0xFF7A7A7D);
  static const accent = Color(0xFF6C6CFF);
  static const accentHover = Color(0xFF8585FF);
  static const selection = Color(0xFF3D3D6B);
  static const lineNumber = Color(0xFF5A5A5D);
  static const lineHighlight = Color(0xFF252528);
  static const hover = Color(0xFF2A2A2C);

  // Fleet syntax colors - slightly more vibrant
  static const keyword = Color(0xFFCF8E6D);
  static const string = Color(0xFF6AAB73);
  static const comment = Color(0xFF7A7A7D);
  static const function = Color(0xFF56A8F5);
  static const type = Color(0xFF2AACB8);
  static const number = Color(0xFF2AACB8);
  static const variable = Color(0xFFE8E8E8);
}

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

  @override
  State<IdeExample> createState() => _IdeExampleState();
}

class _IdeExampleState extends State<IdeExample> {
  late final IdeController _ideController;
  final Duration _animationDuration = const Duration(milliseconds: 200);

  // Tab States
  int _bottomTabIndex = 0;
  int _activityIndex = 0;

  // Editor state
  int _activeEditorTab = 0;
  String _selectedFile = 'main.dart';

  // File tree expansion state
  final Set<String> _expandedFolders = {'lib', 'lib/src'};

  // Saved layout state for demonstrating save/load
  Map<String, dynamic>? _savedLayoutState;

  // Pane visibility state (updated via onPaneStateChanged callback)
  bool _isLeftVisible = true;
  bool _isRightVisible = true;
  bool _isBottomVisible = true;

  // Maximize state (updated via onMaximizeStateChanged callback)
  bool _isEditorMaximized = false;
  bool _isTerminalMaximized = false;

  @override
  void initState() {
    super.initState();
    _ideController = IdeController(
      // Demonstrates pixel-based sizing with min/max constraints
      leftSize: PaneSize.pixel(280),
      rightSize: PaneSize.pixel(280),
      // Bottom panel with pixel sizing
      bottomSize: PaneSize.pixel(200),
    );
    // Show panels by default
    _ideController.rootController.show(IdePane.right.id);
    _ideController.centerController.show(IdePane.bottom.id);
  }

  @override
  void dispose() {
    _ideController.dispose();
    super.dispose();
  }

  void _onPaneStateChanged(IdePane pane, bool isVisible) {
    setState(() {
      switch (pane) {
        case IdePane.left:
          _isLeftVisible = isVisible;
        case IdePane.right:
          _isRightVisible = isVisible;
        case IdePane.bottom:
          _isBottomVisible = isVisible;
        case IdePane.center:
        case IdePane.centerContainer:
          // These don't need tracking for toggle icons
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return PaneTheme(
      data: const PaneThemeData(
        resizerColor: Colors.transparent,
        resizerHoverColor: IdeColors.accent,
        // Demonstrates keyboard accessibility focus color
        resizerFocusedColor: IdeColors.accentHover,
        resizerThickness: 4.0,
        resizerHitTestThickness: 8.0,
        tabHeaderColor: IdeColors.panel,
        tabBackground: IdeColors.panel,
        tabSelectedBackground: IdeColors.surface,
        tabLabelColor: IdeColors.textMuted,
        tabSelectedLabelColor: IdeColors.text,
      ),
      child: Scaffold(
        body: Container(
          color: IdeColors.background,
          child: Column(
            children: [
              // Title bar - Fleet style (minimal)
              _buildTitleBar(),
              // Main layout with Fleet Islands UI padding
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
                  child: Row(
                    children: [
                      // Slim navigation rail (Fleet style)
                      _buildNavigationRail(),
                      const SizedBox(width: 8),
                      // Main IDE Layout
                      Expanded(
                        child: IdeLayout(
                          controller: _ideController,
                          animationDuration: _animationDuration,
                          // Use callbacks to update UI state
                          onPaneStateChanged: _onPaneStateChanged,
                          onMaximizeStateChanged: (isMaximized) {
                            setState(() {
                              // Determine which pane is maximized based on
                              // which pane ID is set in each controller
                              if (isMaximized) {
                                final centerMaxId = _ideController
                                    .centerController
                                    .maximizedPaneId;
                                if (centerMaxId == IdePane.bottom.id) {
                                  _isTerminalMaximized = true;
                                  _isEditorMaximized = false;
                                } else {
                                  _isEditorMaximized = true;
                                  _isTerminalMaximized = false;
                                }
                              } else {
                                _isEditorMaximized = false;
                                _isTerminalMaximized = false;
                              }
                            });
                          },
                          leftPanelBuilder: (context) => _buildLeftPanel(),
                          rightPanelBuilder: (context) => _buildRightPanel(),
                          bottomPanelBuilder: (context) => _buildBottomPanel(),
                          centerBuilder: (context) => _buildCenterPanel(),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTitleBar() {
    // Fleet style: minimal title bar integrated with window
    return Container(
      height: 38,
      color: IdeColors.background,
      padding: const EdgeInsets.symmetric(horizontal: 8),
      child: Row(
        children: [
          const SizedBox(width: 72), // macOS window controls space
          // Layout control buttons demonstrating pane APIs
          _titleBarAction(Icons.save_outlined, 'Save Layout', () {
            _savedLayoutState = _ideController.save();
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('Layout saved!'),
                duration: Duration(seconds: 1),
              ),
            );
          }),
          _titleBarAction(
            Icons.restore_outlined,
            'Restore Layout',
            _savedLayoutState != null
                ? () {
                    _ideController.load(_savedLayoutState!);
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(
                        content: Text('Layout restored!'),
                        duration: Duration(seconds: 1),
                      ),
                    );
                  }
                : null,
          ),
          _titleBarAction(Icons.restart_alt, 'Reset Sizes', () {
            _ideController.rootController.resetSize(IdePane.left.id);
            _ideController.rootController.resetSize(IdePane.right.id);
            _ideController.centerController.resetSize(IdePane.bottom.id);
          }),
          // Fleet uses a command palette style search in the title bar
          Expanded(
            child: Center(
              child: Container(
                constraints: const BoxConstraints(maxWidth: 400),
                height: 28,
                decoration: BoxDecoration(
                  color: IdeColors.panel,
                  borderRadius: BorderRadius.circular(6),
                  border: Border.all(color: IdeColors.border, width: 1),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Icon(
                      Icons.search,
                      size: 14,
                      color: IdeColors.textMuted,
                    ),
                    const SizedBox(width: 8),
                    Text(
                      _selectedFile,
                      style: const TextStyle(
                        color: IdeColors.textMuted,
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
          // Toggle buttons for panels (right side)
          _titleBarAction(
            Icons.terminal,
            _isBottomVisible ? 'Hide Terminal' : 'Show Terminal',
            () => _ideController.toggleBottom(),
          ),
          _titleBarAction(
            Icons.account_tree_outlined,
            _isRightVisible ? 'Hide Structure' : 'Show Structure',
            () => _ideController.toggleRight(),
          ),
          const SizedBox(width: 4),
          // Show current pane sizes (demonstrates getPixelSize API)
          _buildPaneSizeIndicator(),
        ],
      ),
    );
  }

  Widget _titleBarAction(IconData icon, String tooltip, VoidCallback? onTap) {
    final isEnabled = onTap != null;
    return Tooltip(
      message: tooltip,
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(4),
        child: Padding(
          padding: const EdgeInsets.all(6),
          child: Icon(
            icon,
            size: 16,
            color: isEnabled ? IdeColors.textMuted : IdeColors.border,
          ),
        ),
      ),
    );
  }

  Widget _buildPaneSizeIndicator() {
    // Demonstrates getPixelSize and getFractionalSize APIs
    final leftSize = _ideController.rootController.getPixelSize(
      IdePane.left.id,
    );
    final bottomSize = _ideController.centerController.getPixelSize(
      IdePane.bottom.id,
    );
    final leftVisible = _ideController.rootController.isVisible(
      IdePane.left.id,
    );
    final bottomVisible = _ideController.centerController.isVisible(
      IdePane.bottom.id,
    );

    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (leftVisible && leftSize != null)
          _sizeChip('L: ${leftSize.toInt()}px'),
        if (bottomVisible && bottomSize != null)
          _sizeChip('B: ${bottomSize.toInt()}px'),
        const SizedBox(width: 8),
      ],
    );
  }

  Widget _sizeChip(String text) {
    return Container(
      margin: const EdgeInsets.only(left: 4),
      padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
      decoration: BoxDecoration(
        color: IdeColors.panel,
        borderRadius: BorderRadius.circular(4),
        border: Border.all(color: IdeColors.border, width: 1),
      ),
      child: Text(
        text,
        style: const TextStyle(
          color: IdeColors.textMuted,
          fontSize: 10,
          fontFamily: 'JetBrains Mono',
        ),
      ),
    );
  }

  Widget _buildNavigationRail() {
    // Fleet style: slim, minimal navigation rail with rounded corners
    return Container(
      width: 44,
      decoration: BoxDecoration(
        color: IdeColors.panel,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: IdeColors.panelBorder, width: 1),
      ),
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Column(
        children: [
          _navRailItem(Icons.folder_outlined, 0, 'Files'),
          _navRailItem(Icons.search, 1, 'Search'),
          _navRailItem(Icons.account_tree_outlined, 2, 'Git'),
          _navRailItem(Icons.play_arrow_outlined, 3, 'Run'),
          const Spacer(),
          _navRailItem(Icons.settings_outlined, 5, 'Settings'),
        ],
      ),
    );
  }

  Widget _navRailItem(IconData icon, int index, String tooltip) {
    // Nav item is active if it's selected AND the left pane is visible
    final isActive = _activityIndex == index && index < 5 && _isLeftVisible;
    return Tooltip(
      message: tooltip,
      preferBelow: false,
      waitDuration: const Duration(milliseconds: 500),
      child: InkWell(
        onTap: () {
          setState(() {
            if (index < 5) {
              if (_activityIndex == index) {
                _ideController.toggleLeft();
              } else {
                _activityIndex = index;
                if (!_isLeftVisible) {
                  _ideController.toggleLeft();
                }
              }
            }
          });
        },
        borderRadius: BorderRadius.circular(6),
        child: Container(
          width: 36,
          height: 36,
          margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
          decoration: BoxDecoration(
            color: isActive ? IdeColors.selection : Colors.transparent,
            borderRadius: BorderRadius.circular(6),
          ),
          child: Icon(
            icon,
            size: 20,
            color: isActive ? IdeColors.text : IdeColors.textMuted,
          ),
        ),
      ),
    );
  }

  Widget _buildLeftPanel() {
    // Fleet style: panel with rounded corners (island effect)
    return ClipRRect(
      borderRadius: BorderRadius.circular(8),
      child: Container(
        decoration: BoxDecoration(
          color: IdeColors.panel,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: IdeColors.panelBorder, width: 1),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Panel header - Fleet style (simpler)
            Container(
              height: 40,
              padding: const EdgeInsets.symmetric(horizontal: 12),
              child: Row(
                children: [
                  Text(
                    _getActivityTitle(),
                    style: const TextStyle(
                      color: IdeColors.text,
                      fontSize: 12,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const Spacer(),
                  _panelAction(Icons.add, 'New'),
                  _panelAction(Icons.refresh, 'Refresh'),
                  // Demonstrate maximize for left panel
                  Tooltip(
                    message: _ideController.rootController.isMaximized
                        ? 'Restore'
                        : 'Maximize',
                    child: InkWell(
                      onTap: () => _ideController.rootController.toggleMaximize(
                        IdePane.left.id,
                      ),
                      borderRadius: BorderRadius.circular(4),
                      child: Padding(
                        padding: const EdgeInsets.all(4),
                        child: Icon(
                          _ideController.rootController.isMaximized
                              ? Icons.fullscreen_exit
                              : Icons.fullscreen,
                          size: 16,
                          color: IdeColors.textMuted,
                        ),
                      ),
                    ),
                  ),
                  // Demonstrate hide API
                  Tooltip(
                    message: 'Close Panel',
                    child: InkWell(
                      onTap: () => _ideController.toggleLeft(),
                      borderRadius: BorderRadius.circular(4),
                      child: const Padding(
                        padding: EdgeInsets.all(4),
                        child: Icon(
                          Icons.close,
                          size: 16,
                          color: IdeColors.textMuted,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            // File tree
            Expanded(child: _buildFileTree()),
          ],
        ),
      ),
    );
  }

  String _getActivityTitle() {
    switch (_activityIndex) {
      case 0:
        return 'Files';
      case 1:
        return 'Search';
      case 2:
        return 'Git';
      case 3:
        return 'Run';
      case 4:
        return 'Extensions';
      default:
        return 'Files';
    }
  }

  Widget _panelAction(IconData icon, String tooltip) {
    return Tooltip(
      message: tooltip,
      child: InkWell(
        onTap: () {},
        borderRadius: BorderRadius.circular(4),
        child: Padding(
          padding: const EdgeInsets.all(4),
          child: Icon(icon, size: 16, color: IdeColors.textMuted),
        ),
      ),
    );
  }

  Widget _buildFileTree() {
    return ListView(
      padding: const EdgeInsets.only(left: 8),
      children: [
        _folderItem('lib', 0),
        if (_expandedFolders.contains('lib')) ...[
          _folderItem('src', 1, parent: 'lib'),
          if (_expandedFolders.contains('lib/src')) ...[
            _fileItem('ide_layout.dart', 2),
            _fileItem('multi_pane.dart', 2),
            _fileItem('pane_controller.dart', 2),
            _fileItem('pane_entry.dart', 2),
            _fileItem('pane_size.dart', 2),
            _fileItem('pane_theme.dart', 2),
            _fileItem('resizer.dart', 2),
            _fileItem('tabbed_pane.dart', 2),
          ],
          _fileItem('main.dart', 1),
        ],
        _folderItem('test', 0),
        _fileItem('pubspec.yaml', 0),
        _fileItem('README.md', 0),
        _fileItem('.gitignore', 0),
      ],
    );
  }

  Widget _folderItem(String name, int depth, {String? parent}) {
    final fullPath = parent != null ? '$parent/$name' : name;
    final isExpanded = _expandedFolders.contains(fullPath);

    return InkWell(
      onTap: () {
        setState(() {
          if (isExpanded) {
            _expandedFolders.remove(fullPath);
          } else {
            _expandedFolders.add(fullPath);
          }
        });
      },
      child: Container(
        height: 22,
        padding: EdgeInsets.only(left: depth * 16.0 + 4),
        child: Row(
          children: [
            Icon(
              isExpanded ? Icons.expand_more : Icons.chevron_right,
              size: 16,
              color: IdeColors.textMuted,
            ),
            const SizedBox(width: 4),
            Icon(
              isExpanded ? Icons.folder_open : Icons.folder,
              size: 16,
              color: const Color(0xFFDCB67A),
            ),
            const SizedBox(width: 6),
            Text(
              name,
              style: const TextStyle(color: IdeColors.text, fontSize: 13),
            ),
          ],
        ),
      ),
    );
  }

  Widget _fileItem(String name, int depth) {
    final isSelected = _selectedFile == name;
    final icon = _getFileIcon(name);
    final iconColor = _getFileIconColor(name);

    return InkWell(
      onTap: () {
        setState(() {
          _selectedFile = name;
        });
      },
      child: Container(
        height: 22,
        padding: EdgeInsets.only(left: depth * 16.0 + 24),
        decoration: BoxDecoration(
          color: isSelected ? IdeColors.selection : null,
        ),
        child: Row(
          children: [
            Icon(icon, size: 16, color: iconColor),
            const SizedBox(width: 6),
            Text(
              name,
              style: TextStyle(
                color: isSelected ? Colors.white : IdeColors.text,
                fontSize: 13,
              ),
            ),
          ],
        ),
      ),
    );
  }

  IconData _getFileIcon(String name) {
    if (name.endsWith('.dart')) return Icons.code;
    if (name.endsWith('.yaml') || name.endsWith('.yml')) return Icons.settings;
    if (name.endsWith('.md')) return Icons.description;
    if (name.startsWith('.')) return Icons.settings;
    return Icons.insert_drive_file;
  }

  Color _getFileIconColor(String name) {
    if (name.endsWith('.dart')) return IdeColors.type;
    if (name.endsWith('.yaml') || name.endsWith('.yml')) {
      return IdeColors.keyword;
    }
    if (name.endsWith('.md')) return IdeColors.function;
    return IdeColors.textMuted;
  }

  Widget _buildCenterPanel() {
    // Fleet style: editor with rounded corners
    return ClipRRect(
      borderRadius: BorderRadius.circular(8),
      child: Container(
        decoration: BoxDecoration(
          color: IdeColors.surface,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: IdeColors.panelBorder, width: 1),
        ),
        child: Column(
          children: [
            // Editor tabs
            _buildEditorTabs(),
            // Code editor (no breadcrumb - Fleet is minimal)
            Expanded(child: _buildCodeEditor()),
          ],
        ),
      ),
    );
  }

  Widget _buildEditorTabs() {
    final tabs = ['main.dart', 'pane_theme.dart'];

    return Container(
      height: 36,
      decoration: const BoxDecoration(
        color: IdeColors.panel,
        border: Border(
          bottom: BorderSide(color: IdeColors.panelBorder, width: 1),
        ),
      ),
      child: Row(
        children: [
          for (int i = 0; i < tabs.length; i++) _editorTab(tabs[i], i),
          const Spacer(),
          // Maximize editor button
          Tooltip(
            message: _isEditorMaximized ? 'Restore' : 'Maximize',
            child: InkWell(
              onTap: () {
                _ideController.rootController.toggleMaximize(
                  IdePane.centerContainer.id,
                );
                _ideController.centerController.toggleMaximize(
                  IdePane.center.id,
                );
              },
              borderRadius: BorderRadius.circular(4),
              child: Padding(
                padding: const EdgeInsets.all(4),
                child: Icon(
                  _isEditorMaximized ? Icons.fullscreen_exit : Icons.fullscreen,
                  size: 16,
                  color: IdeColors.textMuted,
                ),
              ),
            ),
          ),
          const SizedBox(width: 8),
        ],
      ),
    );
  }

  Widget _editorTab(String name, int index) {
    final isActive = _activeEditorTab == index;

    return GestureDetector(
      onTap: () => setState(() => _activeEditorTab = index),
      child: Container(
        height: 36,
        padding: const EdgeInsets.symmetric(horizontal: 12),
        margin: const EdgeInsets.only(top: 4, left: 4),
        decoration: BoxDecoration(
          color: isActive ? IdeColors.surface : Colors.transparent,
          borderRadius: const BorderRadius.only(
            topLeft: Radius.circular(6),
            topRight: Radius.circular(6),
          ),
        ),
        child: Row(
          children: [
            Icon(Icons.code, size: 14, color: const Color(0xFF2AACB8)),
            const SizedBox(width: 6),
            Text(
              name,
              style: TextStyle(
                color: isActive ? IdeColors.text : IdeColors.textMuted,
                fontSize: 12,
              ),
            ),
            const SizedBox(width: 8),
            if (isActive)
              InkWell(
                onTap: () {},
                borderRadius: BorderRadius.circular(4),
                child: const Icon(
                  Icons.close,
                  size: 14,
                  color: IdeColors.textMuted,
                ),
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildCodeEditor() {
    final lines = _getCodeForFile(_selectedFile);

    return Container(
      color: IdeColors.background,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Line numbers + Code content (scrolls together)
          Expanded(
            child: SingleChildScrollView(
              padding: const EdgeInsets.symmetric(vertical: 8),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // Line numbers
                  SizedBox(
                    width: 50,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      children: List.generate(
                        lines.length,
                        (i) => Container(
                          height: 20,
                          padding: const EdgeInsets.only(right: 12),
                          color: i == 4 ? IdeColors.lineHighlight : null,
                          child: Text(
                            '${i + 1}',
                            style: TextStyle(
                              color: i == 4
                                  ? IdeColors.text
                                  : IdeColors.lineNumber,
                              fontSize: 13,
                              fontFamily: 'JetBrains Mono',
                              height: 1.5,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                  // Code content
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        for (int i = 0; i < lines.length; i++)
                          Container(
                            height: 20,
                            padding: const EdgeInsets.only(left: 4),
                            color: i == 4 ? IdeColors.lineHighlight : null,
                            child: lines[i],
                          ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
          // Minimap
          Container(
            width: 80,
            margin: const EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: IdeColors.sidebar.withValues(alpha: 0.5),
              borderRadius: BorderRadius.circular(2),
            ),
            child: Column(
              children: [
                Container(
                  height: 20,
                  margin: const EdgeInsets.all(4),
                  decoration: BoxDecoration(
                    color: IdeColors.selection.withValues(alpha: 0.5),
                    borderRadius: BorderRadius.circular(1),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  List<Widget> _getCodeForFile(String filename) {
    if (filename == 'main.dart' || filename.endsWith('.dart')) {
      return [
        _codeLine([
          _keyword('import'),
          _plain(' '),
          _string("'package:flutter/material.dart'"),
          _plain(';'),
        ]),
        _codeLine([
          _keyword('import'),
          _plain(' '),
          _string("'package:panes/panes.dart'"),
          _plain(';'),
        ]),
        _codeLine([]),
        _codeLine([
          _keyword('void'),
          _plain(' '),
          _function('main'),
          _plain('() {'),
        ]),
        _codeLine([
          _plain('  '),
          _function('runApp'),
          _plain('('),
          _keyword('const'),
          _plain(' '),
          _type('MyApp'),
          _plain('());'),
        ]),
        _codeLine([_plain('}')]),
        _codeLine([]),
        _codeLine([
          _keyword('class'),
          _plain(' '),
          _type('MyApp'),
          _plain(' '),
          _keyword('extends'),
          _plain(' '),
          _type('StatelessWidget'),
          _plain(' {'),
        ]),
        _codeLine([
          _plain('  '),
          _keyword('const'),
          _plain(' '),
          _type('MyApp'),
          _plain('({'),
          _keyword('super'),
          _plain('.key});'),
        ]),
        _codeLine([]),
        _codeLine([_plain('  '), _keyword('@override')]),
        _codeLine([
          _plain('  '),
          _type('Widget'),
          _plain(' '),
          _function('build'),
          _plain('('),
          _type('BuildContext'),
          _plain(' context) {'),
        ]),
        _codeLine([
          _plain('    '),
          _keyword('return'),
          _plain(' '),
          _type('MaterialApp'),
          _plain('('),
        ]),
        _codeLine([
          _plain('      title: '),
          _string("'Panes Example'"),
          _plain(','),
        ]),
        _codeLine([
          _plain('      home: '),
          _keyword('const'),
          _plain(' '),
          _type('IdeExample'),
          _plain('(),'),
        ]),
        _codeLine([_plain('    );')]),
        _codeLine([_plain('  }')]),
        _codeLine([_plain('}')]),
        _codeLine([]),
        _codeLine([_comment('// Enhanced IDE layout demonstration')]),
      ];
    } else if (filename == 'pubspec.yaml') {
      return [
        _codeLine([_variable('name'), _plain(': '), _string('panes_example')]),
        _codeLine([
          _variable('description'),
          _plain(': '),
          _string('A demo of the panes package'),
        ]),
        _codeLine([_variable('version'), _plain(': '), _string('1.0.0')]),
        _codeLine([]),
        _codeLine([_variable('environment'), _plain(':')]),
        _codeLine([
          _plain('  '),
          _variable('sdk'),
          _plain(': '),
          _string("'>=3.0.0 <4.0.0'"),
        ]),
      ];
    }
    return [
      _codeLine([_comment('// Select a file to view')]),
    ];
  }

  Widget _codeLine(List<Widget> spans) {
    return Row(children: spans.isEmpty ? [const SizedBox(height: 20)] : spans);
  }

  Widget _keyword(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.keyword,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );
  Widget _string(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.string,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );
  Widget _comment(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.comment,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );
  Widget _function(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.function,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );
  Widget _type(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.type,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );
  Widget _variable(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.variable,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );
  Widget _plain(String text) => Text(
    text,
    style: const TextStyle(
      color: IdeColors.text,
      fontSize: 13,
      fontFamily: 'JetBrains Mono',
      height: 1.5,
    ),
  );

  Widget _buildRightPanel() {
    // Fleet style: outline panel with rounded corners
    return ClipRRect(
      borderRadius: BorderRadius.circular(8),
      child: Container(
        decoration: BoxDecoration(
          color: IdeColors.panel,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: IdeColors.panelBorder, width: 1),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              height: 40,
              padding: const EdgeInsets.symmetric(horizontal: 12),
              child: Row(
                children: [
                  const Text(
                    'Structure',
                    style: TextStyle(
                      color: IdeColors.text,
                      fontSize: 12,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const Spacer(),
                  _panelAction(Icons.filter_list, 'Filter'),
                  // Demonstrate maximize for right panel
                  Tooltip(
                    message: _ideController.rootController.isMaximized
                        ? 'Restore'
                        : 'Maximize',
                    child: InkWell(
                      onTap: () => _ideController.rootController.toggleMaximize(
                        IdePane.right.id,
                      ),
                      borderRadius: BorderRadius.circular(4),
                      child: Padding(
                        padding: const EdgeInsets.all(4),
                        child: Icon(
                          _ideController.rootController.isMaximized
                              ? Icons.fullscreen_exit
                              : Icons.fullscreen,
                          size: 16,
                          color: IdeColors.textMuted,
                        ),
                      ),
                    ),
                  ),
                  // Demonstrate hide API
                  Tooltip(
                    message: 'Close Panel',
                    child: InkWell(
                      onTap: () => _ideController.toggleRight(),
                      borderRadius: BorderRadius.circular(4),
                      child: const Padding(
                        padding: EdgeInsets.all(4),
                        child: Icon(
                          Icons.close,
                          size: 16,
                          color: IdeColors.textMuted,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            Expanded(
              child: ListView(
                padding: const EdgeInsets.symmetric(horizontal: 8),
                children: [
                  _outlineItem('MyApp', Icons.class_, 0),
                  _outlineItem('build()', Icons.functions, 1),
                  _outlineItem('IdeExample', Icons.class_, 0),
                  _outlineItem('_IdeExampleState', Icons.class_, 1),
                  _outlineItem('build()', Icons.functions, 2),
                  _outlineItem('_buildFileTree()', Icons.functions, 2),
                  _outlineItem('_buildCodeEditor()', Icons.functions, 2),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _outlineItem(String name, IconData icon, int depth) {
    return Padding(
      padding: EdgeInsets.only(left: depth * 12.0, top: 4, bottom: 4),
      child: Row(
        children: [
          Icon(icon, size: 14, color: IdeColors.accent),
          const SizedBox(width: 8),
          Text(
            name,
            style: const TextStyle(color: IdeColors.text, fontSize: 12),
          ),
        ],
      ),
    );
  }

  Widget _buildBottomPanel() {
    // Fleet style: wrap TabbedPane in rounded container
    return ClipRRect(
      borderRadius: BorderRadius.circular(8),
      child: Container(
        decoration: BoxDecoration(
          color: IdeColors.panel,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: IdeColors.panelBorder, width: 1),
        ),
        child: TabbedPane(
          selectedIndex: _bottomTabIndex,
          onTabSelected: (index) => setState(() => _bottomTabIndex = index),
          labels: const ['Terminal', 'Problems', 'Output', 'Debug'],
          icons: const [
            Icons.terminal,
            Icons.error_outline,
            Icons.text_snippet,
            Icons.bug_report,
          ],
          actions: [
            _panelAction(Icons.add, 'New Terminal'),
            _panelAction(Icons.splitscreen, 'Split'),
            const SizedBox(width: 4),
            // Demonstrate maximize/restore for bottom panel (full maximize)
            Tooltip(
              message: _isTerminalMaximized ? 'Restore' : 'Maximize',
              child: InkWell(
                onTap: () {
                  // Maximize both controllers for true full-screen terminal
                  _ideController.rootController.toggleMaximize(
                    IdePane.centerContainer.id,
                  );
                  _ideController.centerController.toggleMaximize(
                    IdePane.bottom.id,
                  );
                },
                borderRadius: BorderRadius.circular(4),
                child: Padding(
                  padding: const EdgeInsets.all(4),
                  child: Icon(
                    _isTerminalMaximized
                        ? Icons.fullscreen_exit
                        : Icons.fullscreen,
                    size: 16,
                    color: IdeColors.textMuted,
                  ),
                ),
              ),
            ),
            const SizedBox(width: 4),
            InkWell(
              onTap: () => _ideController.toggleBottom(),
              borderRadius: BorderRadius.circular(4),
              child: const Padding(
                padding: EdgeInsets.all(4),
                child: Icon(Icons.close, size: 16, color: IdeColors.textMuted),
              ),
            ),
            const SizedBox(width: 8),
          ],
          tabBuilder: (context, index) {
            if (index == 0) return _buildTerminalContent();
            return _buildPlaceholderContent(index);
          },
        ),
      ),
    );
  }

  Widget _buildTerminalContent() {
    return Container(
      color: IdeColors.surface,
      padding: const EdgeInsets.all(12),
      child: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _terminalLine(
              'simon@macbook',
              'panes %',
              'flutter run -d macos',
              isPrompt: true,
            ),
            const SizedBox(height: 4),
            _terminalOutput(
              'Launching lib/main.dart on macOS in debug mode...',
              IdeColors.text,
            ),
            _terminalOutput('Building macOS application...', IdeColors.text),
            const SizedBox(height: 4),
            _terminalOutput(
              '✓ Built build/macos/Build/Products/Debug/example.app',
              IdeColors.string,
            ),
            const SizedBox(height: 8),
            _terminalOutput('Syncing files to device macOS...', IdeColors.text),
            _terminalOutput('  4,521ms (!) ', IdeColors.keyword),
            const SizedBox(height: 8),
            _terminalOutput('Flutter run key commands.', IdeColors.textMuted),
            _terminalOutput('r  Hot reload. 🔥🔥🔥', IdeColors.textMuted),
            _terminalOutput('R  Hot restart.', IdeColors.textMuted),
            _terminalOutput('q  Quit.', IdeColors.textMuted),
            const SizedBox(height: 16),
            _terminalLine('simon@macbook', 'panes %', '', isPrompt: true),
          ],
        ),
      ),
    );
  }

  Widget _terminalLine(
    String user,
    String path,
    String command, {
    bool isPrompt = false,
  }) {
    return Row(
      children: [
        Text(
          user,
          style: const TextStyle(
            color: IdeColors.string,
            fontSize: 12,
            fontFamily: 'JetBrains Mono',
          ),
        ),
        const Text(' ', style: TextStyle(fontSize: 12)),
        Text(
          path,
          style: const TextStyle(
            color: IdeColors.function,
            fontSize: 12,
            fontFamily: 'JetBrains Mono',
          ),
        ),
        const Text(' ', style: TextStyle(fontSize: 12)),
        Text(
          command,
          style: const TextStyle(
            color: IdeColors.text,
            fontSize: 12,
            fontFamily: 'JetBrains Mono',
          ),
        ),
        if (isPrompt && command.isEmpty)
          Container(width: 8, height: 14, color: IdeColors.accent),
      ],
    );
  }

  Widget _terminalOutput(String text, Color color) {
    return Padding(
      padding: const EdgeInsets.only(left: 0),
      child: Text(
        text,
        style: TextStyle(
          color: color,
          fontSize: 12,
          fontFamily: 'JetBrains Mono',
        ),
      ),
    );
  }

  Widget _buildPlaceholderContent(int index) {
    String text;
    switch (index) {
      case 1:
        text = 'No problems have been detected in the workspace.';
        break;
      case 2:
        text = 'Output currently empty.';
        break;
      case 3:
        text = 'Debug console ready.';
        break;
      default:
        text = 'Empty';
    }

    return Container(
      color: IdeColors.background,
      padding: const EdgeInsets.all(12),
      alignment: Alignment.topLeft,
      child: Text(
        text,
        style: const TextStyle(color: IdeColors.textMuted, fontSize: 13),
      ),
    );
  }
}
1
likes
160
points
0
downloads

Publisher

verified publishersofluffy.io

Weekly Downloads

A powerful Flutter package for creating complex, resizable, multi-pane layouts. Ideal for building IDE-like interfaces, advanced dashboards, and desktop applications.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on panes