feat(note): page background templates (rnote-style)
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.
This commit is contained in:
2026-06-24 23:47:29 +08:00
parent 46589a4c87
commit c800295c12
6 changed files with 303 additions and 2 deletions

View File

@@ -231,6 +231,22 @@ void main() {
expect(reparsed.strokes[0]!.single.id, 's0');
});
test('background name round-trips through the sidecar', () {
final original = BadnoteSidecar(docType: 'notebook', background: 'cornell');
final reparsed = _roundTrip(original);
expect(reparsed.background, 'cornell');
// Omitted when null (no key bloat for blank/legacy sidecars).
expect(BadnoteSidecar().toJson().containsKey('background'), isFalse);
});
test('missing background decodes to null (back-compat → blank)', () {
final json =
jsonDecode(jsonEncode(BadnoteSidecar().toJson())) as Map<String, dynamic>;
expect(json.containsKey('background'), isFalse);
final reparsed = BadnoteSidecar.fromJson(json);
expect(reparsed.background, isNull);
});
test('missing scratchpad defaults to 4000x4000 empty pad', () {
final json = SidecarScratchLink(
link: const ScratchLink(

View File

@@ -0,0 +1,41 @@
// test/note_background_test.dart
//
// Covers the NoteBackground template enum decode (back-compat: missing/unknown →
// blank) and the painter's shouldRepaint reacting to a background change.
import 'package:flutter_test/flutter_test.dart';
import 'package:badnote/editor/canvas/note_background.dart';
void main() {
group('noteBackgroundFromName', () {
test('decodes each enum name round-trip', () {
for (final b in NoteBackground.values) {
expect(noteBackgroundFromName(b.name), b);
}
});
test('null → blank (back-compat for legacy sidecars)', () {
expect(noteBackgroundFromName(null), NoteBackground.blank);
});
test('unknown name → blank (forward-compat)', () {
expect(noteBackgroundFromName('isometric'), NoteBackground.blank);
expect(noteBackgroundFromName(''), NoteBackground.blank);
});
});
group('NoteBackgroundPainter.shouldRepaint', () {
test('repaints when the background changes', () {
const a = NoteBackgroundPainter(NoteBackground.blank);
const b = NoteBackgroundPainter(NoteBackground.dots);
expect(a.shouldRepaint(b), isTrue);
});
test('does not repaint when the background is identical', () {
const a = NoteBackgroundPainter(NoteBackground.grid);
const b = NoteBackgroundPainter(NoteBackground.grid);
expect(a.shouldRepaint(b), isFalse);
});
});
}