Skip to content

Repository files navigation

Novident Split View

The library manages which buffers are visible and how they are organized — opening, splitting, stacking, swapping, resizing and closing panes — while you decide what every pane renders (an editor, a free form editor, a binder, anything).

Drag a node from your binder tree, drop it on a pane, and the view splits exactly where the cursor says.

Image

See images Image Image Image

How it works — the 30-second model

Three pieces, each with exactly one job:

Piece Job
SplitViewController What is open and how it is arranged. A plain ChangeNotifier you create and keep.
SplitViewProvider + SplitViewWeights The sizes of panes/columns. Mounted once, above your MaterialApp.
paneBuilder What each pane renders. You receive an id, you return a widget.

The controller only stores ids (plain Strings). It never touches your widgets or your data: an id means whatever you want it to mean.

Getting started

dependencies:
  novident_split_view: <latest>

A minimal, complete app

Copy-paste and run — no nodes, no trees, no extra setup:

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

void main() {
  runApp(
    // 1. Pane sizes live ABOVE the app, so they survive any rebuild
    //    or navigation below. One line, done once.
    SplitViewProvider(
      weights: SplitViewWeights.twoGrid(),
      child: const MaterialApp(home: Workspace()),
    ),
  );
}

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

  @override
  State<Workspace> createState() => _WorkspaceState();
}

class _WorkspaceState extends State<Workspace> {
  // 2. The controller decides WHAT is open and WHERE.
  final SplitViewController controller = SplitViewController();

  @override
  void initState() {
    super.initState();
    controller.open('notes'); // first buffer: takes the whole area
    controller.insertPane(    // second buffer: stacked below the first
      column: 0,
      pane: 0,
      zone: SplitZone.bottom,
      nodeId: 'draft',
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 3. The split view calls your paneBuilder for every open id.
      body: NovSplitView(
        controller: controller,
        configuration: SplitViewConfiguration(
          paneBuilder: (context, pane) => PaneHeader(
            draggable: true,          // drag the bar to move/swap panes
            title: Text(pane.nodeId), // the close button needs NO wiring
            child: Center(child: Text('Content of "${pane.nodeId}"')),
          ),
        ),
      ),
    );
  }
}

That is the whole integration. Running it you already have:

  • Two stacked panes (by default) with a draggable divider between them.
  • Headers with a working close button (PaneHeader closes its own pane through the library — no callback needed).
  • Pane moving: drag a header onto the other pane's center to swap them; with a single column, drag it to a screen edge to create a second column.

pane.nodeId is just the string you passed to open/insertPane — map it to your own models however you like.

Optional: accept drops from your own tree

Only needed when users drag things into the split view (e.g. rows from a novident_tree_view binder). The drag payload is a novident_nodes Node; implement the mixin on the classes that may be dropped:

class File extends Node implements SplitDragAndDropMixin {
  @override
  bool isPaneDraggable() => true;                // may enter the split view

  @override
  bool isSplitZoneValid(SplitZone zone) => true; // accepts every drop zone
}

Nothing else: while dragging, translucent shadows preview exactly where the node will land, and on drop its node.id becomes the pane id your paneBuilder receives.

Everyday operations

controller.open('id');                  // Focus, Replace or Open a new column when required 
controller.forceOpen('id');             // Always open a new column at the end
controller.close('id');                 // close the first pane showing it
controller.isOpen('id');                // is it visible somewhere?
controller.locate('id');                // (column, pane) or null
controller.focusPane(column, pane);     // move the focus
controller.notifyNodeChanged('id');     // sync duplicated panes (opt-in)

// From anywhere inside a pane subtree:
SplitViewScope.rebuildPane(context);                 // refresh my pane
SplitViewScope.rebuildPanesShowing(context, 'id');   // refresh a node's panes

Features

  • Two-level grid layout by default: a global row of columns; each column stacks panes vertically with weights isolated from the rest.
  • Id-based state: your nodes never leave their own tree — the controller stores strings, rendering is always yours.
  • Drag & drop splitting with zone shadows, cursor tolerance bands and no dead zones.
  • Pane dragging: move panes between columns or swap two panes by dropping one on the center of the other.
  • Resizable panes with per-app persistable weights.
  • Stable pane identity: the same document open in several panes, each with its own size and widget state.
  • Duplicated-pane sync via content revisions or your own store.
  • Limits (maxPanes, maxColumns) enforced everywhere — invalid drops paint the shadow red before you release.

Documentation

Doc What it covers
Architecture The two-level grid model, id-based state, stable pane identity — and why each decision was made
Controller Every SplitViewController operation, the duplicate rules, limits and content revisions
Zones & drag and drop SplitZone, tolerance bands, global edges, both drag payloads, indicators
Weights & resizing SplitViewWeights, the provider, dividers, and duplicated-pane weight policies
Widgets NovSplitView, PaneHeader, PaneDragHandle, SplitViewScope, PaneContext
Recipes The full example walkthrough: how it is built and why

Ecosystem

Additional information

Issues and contributions are welcome at the Novident repositories

About

A powerful split view for Flutter with id-based pane management, drag & drop splitting, pane moving/swapping, resizable panes, and duplicated-pane sync.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages