2026-06-02 23:28:39 +02:00
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
import '../../models/slide.dart';
|
|
|
|
|
|
import '../../theme/app_theme.dart';
|
2026-06-04 02:30:03 +02:00
|
|
|
|
import '../../l10n/app_localizations.dart';
|
2026-06-02 23:28:39 +02:00
|
|
|
|
|
|
|
|
|
|
class AddSlideDialog extends StatelessWidget {
|
|
|
|
|
|
const AddSlideDialog({super.key});
|
|
|
|
|
|
|
|
|
|
|
|
static Future<SlideType?> show(BuildContext context) {
|
|
|
|
|
|
return showDialog<SlideType>(
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
builder: (_) => const AddSlideDialog(),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const _types = [
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
(SlideType.title, 'Titelpagina'),
|
|
|
|
|
|
(SlideType.section, 'Tussentitel'),
|
|
|
|
|
|
(SlideType.bullets, 'Alleen Bullets'),
|
|
|
|
|
|
(SlideType.twoBullets, 'Twee Bulletkolommen'),
|
|
|
|
|
|
(SlideType.bulletsImage, 'Bullets + Afbeelding'),
|
|
|
|
|
|
(SlideType.twoImages, 'Twee Afbeeldingen'),
|
|
|
|
|
|
(SlideType.image, 'Grote Afbeelding'),
|
|
|
|
|
|
(SlideType.video, 'Video'),
|
|
|
|
|
|
(SlideType.quote, 'Quote'),
|
|
|
|
|
|
(SlideType.table, 'Tabel'),
|
|
|
|
|
|
(SlideType.chart, 'Grafiek'),
|
|
|
|
|
|
(SlideType.code, 'Broncode'),
|
|
|
|
|
|
(SlideType.freeMarkdown, 'Vrije Markdown'),
|
2026-06-02 23:28:39 +02:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-06-04 02:30:03 +02:00
|
|
|
|
final l10n = context.l10n;
|
2026-06-02 23:28:39 +02:00
|
|
|
|
return CallbackShortcuts(
|
|
|
|
|
|
bindings: {
|
|
|
|
|
|
const SingleActivator(LogicalKeyboardKey.escape): () =>
|
|
|
|
|
|
Navigator.pop(context),
|
|
|
|
|
|
},
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
child: AlertDialog(
|
|
|
|
|
|
title: Text(l10n.d('Slide type kiezen')),
|
|
|
|
|
|
content: SizedBox(
|
|
|
|
|
|
width: 440,
|
|
|
|
|
|
// Reading-order tabbing through the cards; the first one takes
|
|
|
|
|
|
// focus so the dialog is fully keyboard-operable right away.
|
|
|
|
|
|
child: FocusTraversalGroup(
|
|
|
|
|
|
policy: ReadingOrderTraversalPolicy(),
|
2026-06-02 23:28:39 +02:00
|
|
|
|
child: Wrap(
|
|
|
|
|
|
spacing: 10,
|
|
|
|
|
|
runSpacing: 10,
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
children: [
|
|
|
|
|
|
for (var i = 0; i < _types.length; i++)
|
|
|
|
|
|
_TypeCard(
|
|
|
|
|
|
type: _types[i].$1,
|
|
|
|
|
|
label: l10n.d(_types[i].$2),
|
|
|
|
|
|
autofocus: i == 0,
|
|
|
|
|
|
onTap: () => Navigator.pop(context, _types[i].$1),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-06-02 23:28:39 +02:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
actions: [
|
|
|
|
|
|
TextButton(
|
|
|
|
|
|
onPressed: () => Navigator.pop(context),
|
|
|
|
|
|
child: Text(l10n.t('cancel')),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-06-02 23:28:39 +02:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _TypeCard extends StatelessWidget {
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
final SlideType type;
|
2026-06-02 23:28:39 +02:00
|
|
|
|
final String label;
|
|
|
|
|
|
final VoidCallback onTap;
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
final bool autofocus;
|
2026-06-02 23:28:39 +02:00
|
|
|
|
|
|
|
|
|
|
const _TypeCard({
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
required this.type,
|
2026-06-02 23:28:39 +02:00
|
|
|
|
required this.label,
|
|
|
|
|
|
required this.onTap,
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
this.autofocus = false,
|
2026-06-02 23:28:39 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
return Semantics(
|
|
|
|
|
|
button: true,
|
|
|
|
|
|
child: InkWell(
|
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
|
autofocus: autofocus,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
|
|
|
|
|
focusColor: AppTheme.accent.withValues(alpha: 0.14),
|
|
|
|
|
|
hoverColor: AppTheme.accent.withValues(alpha: 0.06),
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
padding: const EdgeInsets.all(8),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
border: Border.all(color: const Color(0xFFCBD5E1)),
|
|
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
// A stylised wireframe of the layout, so the card shows what
|
|
|
|
|
|
// the slide will look like instead of an abstract icon.
|
|
|
|
|
|
ExcludeSemantics(
|
|
|
|
|
|
child: ClipRRect(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
|
child: AspectRatio(
|
|
|
|
|
|
aspectRatio: 16 / 9,
|
|
|
|
|
|
child: CustomPaint(
|
|
|
|
|
|
painter: SlideTypePreviewPainter(type: type),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 6),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
label,
|
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
maxLines: 2,
|
|
|
|
|
|
style: const TextStyle(fontSize: 11, height: 1.15),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-06-02 23:28:39 +02:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
|
|
|
|
|
|
/// Paints a miniature 16:9 wireframe of a slide layout, in the spirit of the
|
|
|
|
|
|
/// layout pickers in other presentation tools: title bars, text lines, image
|
|
|
|
|
|
/// placeholders. All geometry lives on a 160×90 design canvas and is scaled
|
|
|
|
|
|
/// to whatever size the card provides.
|
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
|
class SlideTypePreviewPainter extends CustomPainter {
|
|
|
|
|
|
final SlideType type;
|
|
|
|
|
|
|
|
|
|
|
|
/// Wireframe palette: dark bars for titles, soft bars for body text.
|
|
|
|
|
|
static const _canvas = Color(0xFFF8FAFC);
|
|
|
|
|
|
static const _ink = Color(0xFF334155);
|
|
|
|
|
|
static const _soft = Color(0xFFB6C2D2);
|
|
|
|
|
|
static const _fill = Color(0xFFE2E8F0);
|
|
|
|
|
|
static const _accent = AppTheme.accent;
|
|
|
|
|
|
|
|
|
|
|
|
const SlideTypePreviewPainter({required this.type});
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void paint(Canvas canvas, Size size) {
|
|
|
|
|
|
final u = size.width / 160;
|
|
|
|
|
|
canvas.scale(u);
|
|
|
|
|
|
canvas.drawRect(const Rect.fromLTWH(0, 0, 160, 90), _paint(_canvas));
|
|
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case SlideType.title:
|
|
|
|
|
|
_bar(canvas, 30, 34, 100, 12, _ink);
|
|
|
|
|
|
_bar(canvas, 45, 53, 70, 7, _accent);
|
|
|
|
|
|
case SlideType.section:
|
|
|
|
|
|
_bar(canvas, 16, 36, 5, 24, _accent);
|
|
|
|
|
|
_bar(canvas, 30, 38, 86, 11, _ink);
|
|
|
|
|
|
_bar(canvas, 30, 54, 52, 6, _soft);
|
|
|
|
|
|
case SlideType.bullets:
|
|
|
|
|
|
_bar(canvas, 14, 12, 84, 9, _ink);
|
|
|
|
|
|
_bullets(canvas, 14, 34, 110, 4);
|
|
|
|
|
|
case SlideType.twoBullets:
|
|
|
|
|
|
_bar(canvas, 14, 12, 84, 9, _ink);
|
|
|
|
|
|
_bullets(canvas, 14, 32, 56, 3);
|
|
|
|
|
|
_bullets(canvas, 90, 32, 56, 3);
|
|
|
|
|
|
case SlideType.bulletsImage:
|
|
|
|
|
|
_bar(canvas, 14, 12, 66, 9, _ink);
|
|
|
|
|
|
_bullets(canvas, 14, 32, 60, 3);
|
|
|
|
|
|
_imageBox(canvas, 90, 26, 56, 50);
|
|
|
|
|
|
case SlideType.twoImages:
|
|
|
|
|
|
_imageBox(canvas, 12, 16, 64, 46);
|
|
|
|
|
|
_imageBox(canvas, 84, 16, 64, 46);
|
|
|
|
|
|
_bar(canvas, 20, 68, 48, 5, _soft);
|
|
|
|
|
|
_bar(canvas, 92, 68, 48, 5, _soft);
|
|
|
|
|
|
case SlideType.image:
|
|
|
|
|
|
_imageBox(canvas, 10, 10, 140, 70);
|
|
|
|
|
|
case SlideType.video:
|
|
|
|
|
|
_imageBox(canvas, 10, 10, 140, 70, pictogram: false);
|
|
|
|
|
|
canvas.drawCircle(const Offset(80, 45), 14, _paint(Colors.white));
|
|
|
|
|
|
final play = Path()
|
|
|
|
|
|
..moveTo(75, 37)
|
|
|
|
|
|
..lineTo(89, 45)
|
|
|
|
|
|
..lineTo(75, 53)
|
|
|
|
|
|
..close();
|
|
|
|
|
|
canvas.drawPath(play, _paint(_ink));
|
|
|
|
|
|
case SlideType.quote:
|
|
|
|
|
|
_quoteMark(canvas, 16, 16);
|
|
|
|
|
|
_bar(canvas, 42, 30, 96, 7, _ink);
|
|
|
|
|
|
_bar(canvas, 42, 43, 78, 7, _ink);
|
|
|
|
|
|
_bar(canvas, 42, 60, 42, 5, _accent);
|
|
|
|
|
|
case SlideType.table:
|
|
|
|
|
|
_bar(canvas, 14, 16, 132, 14, _soft, radius: 2);
|
|
|
|
|
|
final line = _paint(_ink.withValues(alpha: 0.45))..strokeWidth = 1.5;
|
|
|
|
|
|
for (var r = 1; r <= 4; r++) {
|
|
|
|
|
|
canvas.drawLine(
|
|
|
|
|
|
Offset(14, 16 + r * 14),
|
|
|
|
|
|
Offset(146, 16 + r * 14),
|
|
|
|
|
|
line,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (var c = 0; c <= 3; c++) {
|
|
|
|
|
|
canvas.drawLine(
|
|
|
|
|
|
Offset(14 + c * 44, 16),
|
|
|
|
|
|
Offset(14 + c * 44, 72),
|
|
|
|
|
|
line,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
case SlideType.chart:
|
|
|
|
|
|
final axis = _paint(_soft)..strokeWidth = 2;
|
|
|
|
|
|
canvas.drawLine(const Offset(20, 14), const Offset(20, 74), axis);
|
|
|
|
|
|
canvas.drawLine(const Offset(20, 74), const Offset(148, 74), axis);
|
|
|
|
|
|
_bar(canvas, 34, 50, 18, 24, _soft, radius: 2);
|
|
|
|
|
|
_bar(canvas, 64, 36, 18, 38, _accent, radius: 2);
|
|
|
|
|
|
_bar(canvas, 94, 44, 18, 30, _soft, radius: 2);
|
|
|
|
|
|
_bar(canvas, 124, 24, 18, 50, _accent, radius: 2);
|
|
|
|
|
|
case SlideType.code:
|
|
|
|
|
|
_bar(canvas, 10, 10, 140, 70, const Color(0xFF1E293B), radius: 4);
|
|
|
|
|
|
_bar(canvas, 20, 22, 44, 6, const Color(0xFF7DD3A7), radius: 3);
|
|
|
|
|
|
_bar(canvas, 30, 34, 64, 6, const Color(0xFF93B8F8), radius: 3);
|
|
|
|
|
|
_bar(canvas, 30, 46, 50, 6, const Color(0xFFE2C08D), radius: 3);
|
|
|
|
|
|
_bar(canvas, 20, 58, 32, 6, const Color(0xFF7DD3A7), radius: 3);
|
|
|
|
|
|
case SlideType.freeMarkdown:
|
|
|
|
|
|
_bar(canvas, 14, 12, 10, 9, _accent, radius: 2);
|
|
|
|
|
|
_bar(canvas, 28, 12, 62, 9, _ink);
|
|
|
|
|
|
_bar(canvas, 14, 32, 120, 6, _soft);
|
|
|
|
|
|
_bar(canvas, 14, 44, 132, 6, _soft);
|
|
|
|
|
|
_bar(canvas, 14, 56, 92, 6, _soft);
|
|
|
|
|
|
_bar(canvas, 14, 68, 110, 6, _soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _bar(
|
|
|
|
|
|
Canvas canvas,
|
|
|
|
|
|
double x,
|
|
|
|
|
|
double y,
|
|
|
|
|
|
double w,
|
|
|
|
|
|
double h,
|
|
|
|
|
|
Color color, {
|
|
|
|
|
|
double? radius,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
canvas.drawRRect(
|
|
|
|
|
|
RRect.fromRectAndRadius(
|
|
|
|
|
|
Rect.fromLTWH(x, y, w, h),
|
|
|
|
|
|
Radius.circular(radius ?? h / 2),
|
|
|
|
|
|
),
|
|
|
|
|
|
_paint(color),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A column of bullet points: accent dot plus a soft text line.
|
|
|
|
|
|
void _bullets(Canvas canvas, double x, double y, double w, int count) {
|
|
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
|
|
|
|
final dy = y + i * 13.0;
|
|
|
|
|
|
canvas.drawCircle(Offset(x + 3, dy + 3), 3, _paint(_accent));
|
|
|
|
|
|
_bar(canvas, x + 11, dy, w * (i.isEven ? 1.0 : 0.74), 6, _soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Image placeholder: filled box with a sun and mountains pictogram.
|
|
|
|
|
|
void _imageBox(
|
|
|
|
|
|
Canvas canvas,
|
|
|
|
|
|
double x,
|
|
|
|
|
|
double y,
|
|
|
|
|
|
double w,
|
|
|
|
|
|
double h, {
|
|
|
|
|
|
bool pictogram = true,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
_bar(canvas, x, y, w, h, _fill, radius: 4);
|
|
|
|
|
|
if (!pictogram) return;
|
|
|
|
|
|
final dark = _paint(_soft);
|
|
|
|
|
|
canvas.drawCircle(Offset(x + w * 0.28, y + h * 0.30), h * 0.11, dark);
|
|
|
|
|
|
final hills = Path()
|
|
|
|
|
|
..moveTo(x + w * 0.08, y + h * 0.88)
|
|
|
|
|
|
..lineTo(x + w * 0.38, y + h * 0.45)
|
|
|
|
|
|
..lineTo(x + w * 0.58, y + h * 0.72)
|
|
|
|
|
|
..lineTo(x + w * 0.74, y + h * 0.52)
|
|
|
|
|
|
..lineTo(x + w * 0.94, y + h * 0.88)
|
|
|
|
|
|
..close();
|
|
|
|
|
|
canvas.drawPath(hills, dark);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A stylised double quotation mark.
|
|
|
|
|
|
void _quoteMark(Canvas canvas, double x, double y) {
|
|
|
|
|
|
final paint = _paint(_accent);
|
|
|
|
|
|
for (final dx in [0.0, 11.0]) {
|
|
|
|
|
|
canvas.drawCircle(Offset(x + 4 + dx, y + 8), 4, paint);
|
|
|
|
|
|
final tail = Path()
|
|
|
|
|
|
..moveTo(x + dx, y + 8)
|
|
|
|
|
|
..quadraticBezierTo(x + dx, y + 17, x + 7 + dx, y + 18)
|
|
|
|
|
|
..lineTo(x + 7 + dx, y + 14)
|
|
|
|
|
|
..quadraticBezierTo(x + 4 + dx, y + 13, x + 4 + dx, y + 8)
|
|
|
|
|
|
..close();
|
|
|
|
|
|
canvas.drawPath(tail, paint);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
bool shouldRepaint(SlideTypePreviewPainter old) => old.type != type;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Paint _paint(Color color) => Paint()..color = color;
|