diff --git a/lib/screens/split_view_screen.dart b/lib/screens/split_view_screen.dart index 89fb926..cb811b2 100644 --- a/lib/screens/split_view_screen.dart +++ b/lib/screens/split_view_screen.dart @@ -60,6 +60,9 @@ class _SplitViewState extends State { /// Pan/zoom transform for the scratchpad world (PenCanvas drives this). final TransformationController _scratchTransform = TransformationController(); + /// Set once the initial view has been framed onto existing ink. + bool _scratchCentered = false; + Size get _worldSize => Size(_canvasWidth, _canvasHeight); /// Maps the scratchpad toolbar's [PenTool] to the pen-canvas tool. Shapes and @@ -471,24 +474,79 @@ class _SplitViewState extends State { // Render the world through the performant PenCanvas: strokes normalized // against the current world size; toolbar width is in world pixels, so the // pen-canvas fraction is width / worldWidth. - return Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: PenCanvas( - pageSize: _worldSize, - strokes: penStrokesFromInk(_strokes, _worldSize), - transformationController: _scratchTransform, - tool: _canvasTool, - color: _currentColor, - strokeWidth: _currentStrokeWidth / _canvasWidth, - // The world is huge, so allow zooming further out to survey it. - minScale: 0.1, - maxScale: 8.0, - onStrokeComplete: _onStrokeComplete, - onEraseStroke: _onErase, - pageWidget: const ColoredBox(color: Colors.white), - ), + return LayoutBuilder( + builder: (context, constraints) { + // On first layout, frame the view so existing ink is actually visible + // (otherwise identity shows only the empty top-left corner of the huge + // world). Empty scratchpad falls back to a comfortable 1:1 near origin. + if (!_scratchCentered) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _frameScratchpad( + Size(constraints.maxWidth, constraints.maxHeight)); + setState(() => _scratchCentered = true); + }); + } + return Container( + color: Theme.of(context).scaffoldBackgroundColor, + child: PenCanvas( + pageSize: _worldSize, + strokes: penStrokesFromInk(_strokes, _worldSize), + transformationController: _scratchTransform, + tool: _canvasTool, + color: _currentColor, + strokeWidth: _currentStrokeWidth / _canvasWidth, + // The world is huge, so allow zooming further out to survey it. + minScale: 0.1, + maxScale: 8.0, + onStrokeComplete: _onStrokeComplete, + onEraseStroke: _onErase, + pageWidget: const ColoredBox(color: Colors.white), + ), + ); + }, ); } + + /// Position the scratchpad so existing ink is on-screen. Fits the strokes' + /// world bounding box into [pane] (with padding, scale clamped); for an empty + /// scratchpad, shows the top-left working area at 1:1. + void _frameScratchpad(Size pane) { + if (pane.isEmpty) return; + if (_strokes.isEmpty) { + _scratchTransform.value = Matrix4.identity(); + return; + } + double minX = double.infinity, minY = double.infinity; + double maxX = -double.infinity, maxY = -double.infinity; + for (final s in _strokes) { + for (final p in s.points) { + if (p.x < minX) minX = p.x; + if (p.y < minY) minY = p.y; + if (p.x > maxX) maxX = p.x; + if (p.y > maxY) maxY = p.y; + } + } + if (minX > maxX) { + _scratchTransform.value = Matrix4.identity(); + return; + } + const pad = 80.0; + final boxW = (maxX - minX) + pad * 2; + final boxH = (maxY - minY) + pad * 2; + final scale = + (pane.width / boxW < pane.height / boxH ? pane.width / boxW : pane.height / boxH) + .clamp(0.15, 1.5); + final cx = (minX + maxX) / 2; + final cy = (minY + maxY) / 2; + final tx = pane.width / 2 - scale * cx; + final ty = pane.height / 2 - scale * cy; + _scratchTransform.value = Matrix4.identity() + ..setEntry(0, 0, scale) + ..setEntry(1, 1, scale) + ..setEntry(2, 2, scale) + ..setTranslationRaw(tx, ty, 0); + } } /// A marker linking a scratchpad position to a specific PDF page.