Custom drawing implementation

This commit is contained in:
2026-02-05 17:47:03 +01:00
parent e1d72de718
commit d4d6e41a9d
14 changed files with 1291 additions and 147 deletions

View File

@@ -1,45 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_drawing_board/flutter_drawing_board.dart';
import 'pdf_page_display.dart';
/// Drawing overlay for annotating PDF pages.
///
/// Uses flutter_drawing_board to provide a paint canvas over the PDF.
/// Only working in single-page mode.
class PaintModeLayer extends StatelessWidget {
final PdfPageDisplay pageDisplay;
final DrawingController drawingController;
const PaintModeLayer({
super.key,
required this.pageDisplay,
required this.drawingController,
});
@override
Widget build(BuildContext context) {
return SizedBox.expand(
child: LayoutBuilder(
builder: (context, constraints) {
final maxSize = Size(constraints.maxWidth, constraints.maxHeight);
final (pageSize, _) = pageDisplay.calculateScaledPageSizes(maxSize);
return DrawingBoard(
controller: drawingController,
background: SizedBox(
width: pageSize.width,
height: pageSize.height,
child: pageDisplay,
),
boardConstrained: true,
minScale: 1,
maxScale: 3,
alignment: Alignment.topRight,
boardBoundaryMargin: EdgeInsets.zero,
);
},
),
);
}
}

View File

@@ -4,20 +4,33 @@ import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';
import '../../../core/models/config.dart';
import '../drawing/drawing.dart';
/// Displays PDF pages with optional two-page mode.
/// Displays PDF pages with optional two-page mode and drawing overlay.
class PdfPageDisplay extends StatelessWidget {
final PdfDocument document;
final int numPages;
final int currentPageNumber;
final Config config;
/// Controller for the left/main page drawing
final DrawingController? leftDrawingController;
/// Controller for the right page drawing (two-page mode only)
final DrawingController? rightDrawingController;
/// Whether drawing is enabled
final bool drawingEnabled;
const PdfPageDisplay({
super.key,
required this.document,
required this.numPages,
required this.currentPageNumber,
required this.config,
this.leftDrawingController,
this.rightDrawingController,
this.drawingEnabled = false,
});
/// Whether two-page mode is active and we have enough pages.
@@ -25,55 +38,102 @@ class PdfPageDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [_buildLeftPage(), if (_showTwoPages) _buildRightPage()],
);
}
return LayoutBuilder(
builder: (context, constraints) {
final maxSize = Size(constraints.maxWidth, constraints.maxHeight);
final (leftSize, rightSize) = calculateScaledPageSizes(maxSize);
Widget _buildLeftPage() {
return Expanded(
child: Stack(
children: [
PdfPageView(
key: ValueKey(currentPageNumber),
document: document,
if (_showTwoPages) {
// Two-page mode: pages touch each other and are centered together
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildPage(
pageNumber: currentPageNumber,
pageSize: leftSize,
controller: leftDrawingController,
),
// Only show right page if there is one
if (currentPageNumber < numPages)
_buildPage(
pageNumber: currentPageNumber + 1,
pageSize: rightSize!,
controller: rightDrawingController,
)
else
// Empty space to keep left page position consistent on last page
SizedBox(width: rightSize!.width, height: rightSize.height),
],
),
);
}
// Single page mode
return Center(
child: _buildPage(
pageNumber: currentPageNumber,
maximumDpi: 300,
alignment: _showTwoPages ? Alignment.centerRight : Alignment.center,
pageSize: leftSize,
controller: leftDrawingController,
),
_buildPageIndicator(currentPageNumber),
],
),
);
},
);
}
Widget _buildRightPage() {
final rightPageNumber = currentPageNumber + 1;
return Expanded(
Widget _buildPage({
required int pageNumber,
required Size pageSize,
required DrawingController? controller,
}) {
final pdfPage = SizedBox(
width: pageSize.width,
height: pageSize.height,
child: Stack(
children: [
PdfPageView(
key: ValueKey(rightPageNumber),
key: ValueKey(pageNumber),
document: document,
pageNumber: rightPageNumber,
pageNumber: pageNumber,
maximumDpi: 300,
alignment: Alignment.centerLeft,
alignment: Alignment.center,
),
_buildPageIndicator(rightPageNumber),
_buildPageIndicator(pageNumber, pageSize),
],
),
);
// If no controller, just show the PDF
if (controller == null) {
return pdfPage;
}
// Wrap with DrawingBoard
return DrawingBoard(
boardSize: pageSize,
controller: controller,
drawingEnabled: drawingEnabled,
minScale: 1.0,
maxScale: 3.0,
alignment: Alignment.center,
child: pdfPage,
);
}
Widget _buildPageIndicator(int pageNumber) {
return Positioned.fill(
child: Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(bottom: 5),
child: Text('$pageNumber / $numPages'),
Widget _buildPageIndicator(int pageNumber, Size pageSize) {
return Positioned(
bottom: 5,
left: 0,
right: 0,
child: Center(
child: Text(
'$pageNumber / $numPages',
style: const TextStyle(
fontSize: 12,
color: Colors.black54,
),
),
),
);
}

View File

@@ -4,7 +4,7 @@ import '../../../core/models/config.dart';
import 'pdf_page_display.dart';
/// Callback for page turn events.
typedef PageTurnCallback = void Function(int delta);
typedef PageTurnCallback = dynamic Function(int delta);
/// Gesture layer for touch-based navigation over PDF pages.
///
@@ -14,6 +14,7 @@ typedef PageTurnCallback = void Function(int delta);
/// - Right side: Turn page forward (+1 or +2 in two-page mode)
class TouchNavigationLayer extends StatelessWidget {
final PdfPageDisplay pageDisplay;
final Widget child;
final Config config;
final VoidCallback onToggleFullscreen;
final VoidCallback onExit;
@@ -22,6 +23,7 @@ class TouchNavigationLayer extends StatelessWidget {
const TouchNavigationLayer({
super.key,
required this.pageDisplay,
required this.child,
required this.config,
required this.onToggleFullscreen,
required this.onExit,
@@ -33,7 +35,7 @@ class TouchNavigationLayer extends StatelessWidget {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: (details) => _handleTap(context, details),
child: pageDisplay,
child: child,
);
}