flutter_squiggly_text 0.1.1 copy "flutter_squiggly_text: ^0.1.1" to clipboard
flutter_squiggly_text: ^0.1.1 copied to clipboard

A Flutter widget for rendering text with a customizable squiggly underline.

example/lib/main.dart

import 'package:flutter_squiggly_text_example/l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_squiggly_text/flutter_squiggly_text.dart';

void main() {
  runApp(const SquigglyTextExampleApp());
}

/// Demonstrates the flutter_squiggly_text package.
class SquigglyTextExampleApp extends StatelessWidget {
  /// Creates the example application.
  const SquigglyTextExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Squiggly Text Example',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
          useMaterial3: true,
        ),
        home: const ExamplePage(),
        localizationsDelegates: AppLocalizations.localizationsDelegates,
        supportedLocales: AppLocalizations.supportedLocales);
  }
}

/// Displays the interactive package example.
class ExamplePage extends StatefulWidget {
  /// Creates the example page.
  const ExamplePage({super.key});

  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  static const _colors = <Color>[
    Colors.teal,
    Colors.deepOrange,
    Colors.indigo,
    Colors.pink,
  ];

  final _textController = TextEditingController(
    text: 'Make every word ripple',
  );
  double _amplitude = 3;
  double _wavelength = 10;
  double _gap = 2;
  Color _squiggleColor = Colors.teal;
  SquigglyAnimationStyle _animationStyle =
      SquigglyAnimationStyle.waveAndLetters;
  SquigglyHoverBehavior _hoverBehavior = SquigglyHoverBehavior.none;
  bool _hoverOnly = false;
  bool _respectReducedMotion = false;

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final previewStyle = theme.textTheme.headlineMedium?.copyWith(
      fontWeight: FontWeight.w700,
      color: theme.colorScheme.onSurface,
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('Squiggly Text'),
      ),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(20, 12, 20, 32),
        children: [
          Text(
            'Interactive preview',
            style: theme.textTheme.titleLarge?.copyWith(
              fontWeight: FontWeight.w700,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            'Tune the underline, then compare the layout examples below.',
            style: theme.textTheme.bodyMedium,
          ),
          const SizedBox(height: 20),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  SquigglyText(
                    _textController.text,
                    style: previewStyle,
                    squiggleColor: _squiggleColor,
                    amplitude: _amplitude,
                    wavelength: _wavelength,
                    gap: _gap,
                    textAlign: TextAlign.center,
                    animationStyle: _animationStyle,
                    hoverBehavior: _hoverBehavior,
                    hoverOnly: _hoverOnly,
                    respectReducedMotion: _respectReducedMotion,
                  ),
                  const SizedBox(height: 24),
                  TextField(
                    controller: _textController,
                    decoration: const InputDecoration(
                      labelText: 'Preview text',
                      border: OutlineInputBorder(),
                    ),
                    onChanged: (_) => setState(() {}),
                  ),
                  const SizedBox(height: 16),
                  _SliderSetting(
                    label: 'Amplitude',
                    value: _amplitude,
                    min: 0,
                    max: 8,
                    divisions: 16,
                    onChanged: (value) => setState(() => _amplitude = value),
                  ),
                  _SliderSetting(
                    label: 'Wavelength',
                    value: _wavelength,
                    min: 4,
                    max: 24,
                    divisions: 20,
                    onChanged: (value) => setState(() => _wavelength = value),
                  ),
                  _SliderSetting(
                    label: 'Gap',
                    value: _gap,
                    min: 0,
                    max: 8,
                    divisions: 16,
                    onChanged: (value) => setState(() => _gap = value),
                  ),
                  const SizedBox(height: 8),
                  DropdownButtonFormField<SquigglyAnimationStyle>(
                    initialValue: _animationStyle,
                    decoration: const InputDecoration(
                      labelText: 'Animation style',
                      border: OutlineInputBorder(),
                    ),
                    items: [
                      for (final style in SquigglyAnimationStyle.values)
                        DropdownMenuItem(
                          value: style,
                          child: Text(style.name),
                        ),
                    ],
                    onChanged: (style) {
                      if (style != null) {
                        setState(() => _animationStyle = style);
                      }
                    },
                  ),
                  const SizedBox(height: 12),
                  DropdownButtonFormField<SquigglyHoverBehavior>(
                    initialValue: _hoverBehavior,
                    decoration: const InputDecoration(
                      labelText: 'Pointer interaction',
                      border: OutlineInputBorder(),
                    ),
                    items: [
                      for (final behavior in SquigglyHoverBehavior.values)
                        DropdownMenuItem(
                          value: behavior,
                          child: Text(behavior.name),
                        ),
                    ],
                    onChanged: (behavior) {
                      if (behavior != null) {
                        setState(() => _hoverBehavior = behavior);
                      }
                    },
                  ),
                  SwitchListTile.adaptive(
                    contentPadding: EdgeInsets.zero,
                    title: const Text('Animate only on hover or focus'),
                    value: _hoverOnly,
                    onChanged: (value) => setState(() => _hoverOnly = value),
                  ),
                  SwitchListTile.adaptive(
                    contentPadding: EdgeInsets.zero,
                    title: const Text('Respect reduced-motion settings'),
                    value: _respectReducedMotion,
                    onChanged: (value) =>
                        setState(() => _respectReducedMotion = value),
                  ),
                  const SizedBox(height: 8),
                  Wrap(
                    spacing: 12,
                    children: [
                      for (final color in _colors)
                        ChoiceChip(
                          label: const SizedBox.shrink(),
                          avatar: CircleAvatar(backgroundColor: color),
                          selected: color == _squiggleColor,
                          onSelected: (_) =>
                              setState(() => _squiggleColor = color),
                        ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 28),
          Text(
            'Layout and accessibility',
            style: theme.textTheme.titleLarge?.copyWith(
              fontWeight: FontWeight.w700,
            ),
          ),
          const SizedBox(height: 12),
          const _ExampleSection(
            title: 'Multiline text',
            child: SquigglyText(
              'A longer sentence wraps naturally while every line keeps its underline.',
              style: TextStyle(fontSize: 22, height: 1.35),
              squiggleColor: Colors.deepOrange,
              amplitude: 2.5,
              wavelength: 9,
            ),
          ),
          const _ExampleSection(
            title: 'Right-to-left text',
            child: SquigglyText(
              'Right-to-left layout sample',
              textDirection: TextDirection.rtl,
              textAlign: TextAlign.right,
              style: TextStyle(fontSize: 24),
              squiggleColor: Colors.indigo,
            ),
          ),
          _ExampleSection(
            title: 'Custom semantics label',
            child: Semantics(
              label: 'Accessible underlined greeting',
              child: const SquigglyText(
                'Hello Flutter',
                semanticsLabel: 'Accessible underlined greeting',
                style: TextStyle(fontSize: 24),
                squiggleColor: Colors.pink,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _SliderSetting extends StatelessWidget {
  const _SliderSetting({
    required this.label,
    required this.value,
    required this.min,
    required this.max,
    required this.divisions,
    required this.onChanged,
  });

  final String label;
  final double value;
  final double min;
  final double max;
  final int divisions;
  final ValueChanged<double> onChanged;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        SizedBox(width: 92, child: Text(label)),
        Expanded(
          child: Slider(
            value: value,
            min: min,
            max: max,
            divisions: divisions,
            label: value.toStringAsFixed(1),
            onChanged: onChanged,
          ),
        ),
        SizedBox(
          width: 32,
          child: Text(value.toStringAsFixed(1)),
        ),
      ],
    );
  }
}

class _ExampleSection extends StatelessWidget {
  const _ExampleSection({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(title, style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 8),
          child,
        ],
      ),
    );
  }
}
3
likes
0
points
301
downloads

Publisher

verified publisherortal83cohen.is-a.dev

Weekly Downloads

A Flutter widget for rendering text with a customizable squiggly underline.

Repository (GitHub)
View/report issues

Topics

#flutter #text #typography #widget

License

unknown (license)

Dependencies

characters, flutter

More

Packages that depend on flutter_squiggly_text