// lib/editor/canvas/pen_palette_widgets.dart // // Shared Material 3 chrome for the pen-first editors (PDF, note, slide) so the // floating tool palette looks and behaves identically everywhere — one source // of truth for the inking UI. import 'package:flutter/material.dart'; import '../../l10n/app_localizations.dart'; import '../engine/brush.dart'; /// Localized display name for a brush (single source so all three editors agree). String brushLabel(BrushKind kind, AppLocalizations l) => switch (kind) { BrushKind.fountainPen => l.brushFountainPen, BrushKind.ballpoint => l.brushBallpoint, BrushKind.pencil => l.brushPencil, BrushKind.highlighter => l.brushHighlighter, }; /// English fallback brush name, for the note/slide editors which (like their /// other chrome) use hardcoded English strings rather than [AppLocalizations] /// (their test harness mounts a MaterialApp without localization delegates). /// TODO(brush-l10n-noteslide): localize the note/slide toolbars wholesale. String brushLabelEn(BrushKind kind) => switch (kind) { BrushKind.fountainPen => 'Fountain pen', BrushKind.ballpoint => 'Ballpoint', BrushKind.pencil => 'Pencil', BrushKind.highlighter => 'Highlighter', }; /// The brushes selectable as the PEN tool. The highlighter is its own tool, so /// it is NOT offered here (eraser is also a separate tool). const List kPenToolBrushes = [ BrushKind.fountainPen, BrushKind.ballpoint, BrushKind.pencil, ]; /// Material icon for a brush (used in the brush picker + the pen tool button). IconData brushIcon(BrushKind kind) => switch (kind) { BrushKind.fountainPen => Icons.edit_outlined, // nib pen BrushKind.ballpoint => Icons.create_outlined, // ballpoint BrushKind.pencil => Icons.draw_outlined, // pencil BrushKind.highlighter => Icons.brush_outlined, // marker }; /// A dropdown that selects the active PEN brush (fountain / ballpoint / pencil). /// /// Highlighter and eraser remain separate tools. Tapping the button opens a /// menu of [kPenToolBrushes]; the chosen brush is reported via [onSelected]. /// [labelFor] localizes each brush name so the menu honors the app locale. class BrushPickerButton extends StatelessWidget { const BrushPickerButton({ super.key, required this.selected, required this.active, required this.onSelected, required this.labelFor, required this.tooltip, }); /// The currently selected pen brush. final BrushKind selected; /// True when the pen tool (this brush) is the active tool — drives highlight. final bool active; final ValueChanged onSelected; /// Localized display name for a brush. final String Function(BrushKind) labelFor; final String tooltip; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final iconColor = active ? cs.onSecondaryContainer : cs.onSurfaceVariant; return PopupMenuButton( tooltip: tooltip, initialValue: selected, onSelected: onSelected, itemBuilder: (context) => [ for (final b in kPenToolBrushes) PopupMenuItem( value: b, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(brushIcon(b), size: 20), const SizedBox(width: 10), Text(labelFor(b)), if (b == selected) ...[ const SizedBox(width: 8), Icon(Icons.check, size: 18, color: cs.primary), ], ], ), ), ], child: AnimatedContainer( duration: const Duration(milliseconds: 150), margin: const EdgeInsets.symmetric(horizontal: 2), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 8), decoration: BoxDecoration( color: active ? cs.secondaryContainer : Colors.transparent, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(brushIcon(selected), size: 22, color: iconColor), Icon(Icons.arrow_drop_down, size: 18, color: iconColor), ], ), ), ); } } /// A Material 3 toggle-style icon button for the floating tool palette. class ToolButton extends StatelessWidget { const ToolButton({ super.key, required this.icon, required this.selected, required this.tooltip, required this.onPressed, }); final IconData icon; final bool selected; final String tooltip; /// Tap handler. When null the button renders disabled (dimmed, no ripple). final VoidCallback? onPressed; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final enabled = onPressed != null; final iconColor = !enabled ? cs.onSurfaceVariant.withValues(alpha: 0.38) : selected ? cs.onSecondaryContainer : cs.onSurfaceVariant; return Tooltip( message: tooltip, child: InkWell( borderRadius: BorderRadius.circular(20), onTap: onPressed, child: AnimatedContainer( duration: const Duration(milliseconds: 150), margin: const EdgeInsets.symmetric(horizontal: 2), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: selected ? cs.secondaryContainer : Colors.transparent, borderRadius: BorderRadius.circular(20), ), child: Icon(icon, size: 22, color: iconColor), ), ), ); } } /// A thin vertical divider between palette groups. class PaletteDivider extends StatelessWidget { const PaletteDivider({super.key, required this.cs}); final ColorScheme cs; @override Widget build(BuildContext context) => Container( width: 1, height: 24, margin: const EdgeInsets.symmetric(horizontal: 6), color: cs.outlineVariant, ); } /// A round, tonal icon button (used for the floating back button). class RoundIconButton extends StatelessWidget { const RoundIconButton({ super.key, required this.icon, required this.tooltip, required this.onPressed, }); final IconData icon; final String tooltip; final VoidCallback onPressed; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Material( color: cs.surfaceContainerHigh, elevation: 3, shape: const CircleBorder(), child: IconButton( tooltip: tooltip, icon: Icon(icon), color: cs.onSurfaceVariant, onPressed: onPressed, ), ); } }