Some checks failed
CI / Windows build (push) Has been cancelled
A blank note can show a page-background template painted behind the ink, picked from the toolbar and persisted per notebook. - NoteBackground: blank / dots / ruled / grid / cornell, drawn in page space (scales with zoom), subtle grey. Cornell = left margin + bottom summary rule over a ruled body. - Stored as the enum name in the notebook sidecar (back-compat: missing/unknown -> blank), saved/loaded via SidecarRepository so it restores on reopen. - Picker added to the note tool palette. PDF backgrounds skipped (PDFs have their own page content). analyze clean, 386 tests green.
161 lines
5.2 KiB
Dart
161 lines
5.2 KiB
Dart
// lib/editor/canvas/note_background.dart
|
|
//
|
|
// rnote-style page background TEMPLATES for the blank-note editor. A background
|
|
// is a repeating PATTERN painted in the note page's local pixel space (the
|
|
// `pageWidget` is sized to the page rect inside the InteractiveViewer, so a
|
|
// CustomPainter here scales 1:1 with zoom — no extra transform needed).
|
|
//
|
|
// The choice is per-notebook and persists in the sidecar (stored as the enum
|
|
// `name`; missing/unknown → [NoteBackground.blank] for back-compat).
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// The available page-background templates (rnote: blank + dots/lines/grid +
|
|
/// the Cornell note layout).
|
|
enum NoteBackground {
|
|
/// Plain white sheet, no pattern.
|
|
blank,
|
|
|
|
/// A regular grid of small dots (dotted paper).
|
|
dots,
|
|
|
|
/// Evenly spaced horizontal lines (ruled / lined paper).
|
|
ruled,
|
|
|
|
/// Square grid (graph paper).
|
|
grid,
|
|
|
|
/// Cornell layout: a left cue-column line + a bottom summary line over a
|
|
/// ruled note-taking body.
|
|
cornell,
|
|
}
|
|
|
|
/// Decode a persisted background name (the enum [NoteBackground.name]); unknown
|
|
/// or missing values fall back to [NoteBackground.blank] (back-compat).
|
|
NoteBackground noteBackgroundFromName(String? name) {
|
|
for (final b in NoteBackground.values) {
|
|
if (b.name == name) return b;
|
|
}
|
|
return NoteBackground.blank;
|
|
}
|
|
|
|
/// Localized-ish English display label for the picker menu.
|
|
String noteBackgroundLabel(NoteBackground b) {
|
|
switch (b) {
|
|
case NoteBackground.blank:
|
|
return 'Blank';
|
|
case NoteBackground.dots:
|
|
return 'Dots';
|
|
case NoteBackground.ruled:
|
|
return 'Ruled lines';
|
|
case NoteBackground.grid:
|
|
return 'Grid';
|
|
case NoteBackground.cornell:
|
|
return 'Cornell';
|
|
}
|
|
}
|
|
|
|
/// An icon for the picker menu.
|
|
IconData noteBackgroundIcon(NoteBackground b) {
|
|
switch (b) {
|
|
case NoteBackground.blank:
|
|
return Icons.crop_portrait;
|
|
case NoteBackground.dots:
|
|
return Icons.grain;
|
|
case NoteBackground.ruled:
|
|
return Icons.notes;
|
|
case NoteBackground.grid:
|
|
return Icons.grid_4x4;
|
|
case NoteBackground.cornell:
|
|
return Icons.view_quilt_outlined;
|
|
}
|
|
}
|
|
|
|
/// Paints a [NoteBackground] template behind the ink, in the page's local pixel
|
|
/// space. Spacing is page-relative (a fraction of page width) so the template
|
|
/// looks the same on any logical page size, and the lines are a light, subtle
|
|
/// grey so they sit behind handwriting.
|
|
class NoteBackgroundPainter extends CustomPainter {
|
|
const NoteBackgroundPainter(this.background);
|
|
|
|
final NoteBackground background;
|
|
|
|
/// Pattern spacing as a fraction of the page WIDTH — a ~28-line page.
|
|
static const double _spacingFraction = 1 / 28;
|
|
|
|
static const Color _lineColor = Color(0x1A000000); // ~10% black, subtle grey.
|
|
static const Color _dotColor = Color(0x33000000); // dots a touch darker.
|
|
static const Color _accentColor = Color(0x33335C81); // Cornell margin lines.
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
if (background == NoteBackground.blank) return;
|
|
final spacing = size.width * _spacingFraction;
|
|
if (spacing <= 0) return;
|
|
|
|
switch (background) {
|
|
case NoteBackground.blank:
|
|
break;
|
|
case NoteBackground.dots:
|
|
_paintDots(canvas, size, spacing);
|
|
case NoteBackground.ruled:
|
|
_paintRuled(canvas, size, spacing);
|
|
case NoteBackground.grid:
|
|
_paintGrid(canvas, size, spacing);
|
|
case NoteBackground.cornell:
|
|
_paintCornell(canvas, size, spacing);
|
|
}
|
|
}
|
|
|
|
void _paintDots(Canvas canvas, Size size, double spacing) {
|
|
final paint = Paint()
|
|
..color = _dotColor
|
|
..style = PaintingStyle.fill;
|
|
final r = (spacing * 0.06).clamp(0.6, 2.0);
|
|
for (double y = spacing; y < size.height; y += spacing) {
|
|
for (double x = spacing; x < size.width; x += spacing) {
|
|
canvas.drawCircle(Offset(x, y), r, paint);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _paintRuled(Canvas canvas, Size size, double spacing) {
|
|
final paint = Paint()
|
|
..color = _lineColor
|
|
..strokeWidth = 1.0;
|
|
for (double y = spacing; y < size.height; y += spacing) {
|
|
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
|
}
|
|
}
|
|
|
|
void _paintGrid(Canvas canvas, Size size, double spacing) {
|
|
final paint = Paint()
|
|
..color = _lineColor
|
|
..strokeWidth = 1.0;
|
|
for (double y = spacing; y < size.height; y += spacing) {
|
|
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
|
}
|
|
for (double x = spacing; x < size.width; x += spacing) {
|
|
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
|
|
}
|
|
}
|
|
|
|
void _paintCornell(Canvas canvas, Size size, double spacing) {
|
|
// Ruled body lines.
|
|
_paintRuled(canvas, size, spacing);
|
|
final accent = Paint()
|
|
..color = _accentColor
|
|
..strokeWidth = 1.4;
|
|
// Left cue-column vertical line (~25% of width).
|
|
final cueX = size.width * 0.25;
|
|
// Bottom summary horizontal line (~80% down).
|
|
final summaryY = size.height * 0.80;
|
|
canvas.drawLine(Offset(cueX, 0), Offset(cueX, summaryY), accent);
|
|
canvas.drawLine(Offset(0, summaryY), Offset(size.width, summaryY), accent);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant NoteBackgroundPainter oldDelegate) =>
|
|
oldDelegate.background != background;
|
|
}
|