pico_view 0.4.1 copy "pico_view: ^0.4.1" to clipboard
pico_view: ^0.4.1 copied to clipboard

Mirror a Flutter widget subtree to an external LCD driven by ESP32-P4 firmware over USB, with capacitive touch fed back into the widget tree.

example/lib/main.dart

// Mirrors a clock to a pico-view panel.
//
// The same subtree renders on-screen and on the physical panel, and tapping the
// panel switches the face — the touch arrives as a real pointer event in this
// widget tree. With no device attached everything still runs on-screen; frames
// are simply dropped.

import 'dart:async';
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:pico_view/pico_view.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'pico_view example',
      theme: ThemeData.dark(),
      home: const ClockPage(),
    );
  }
}

class ClockPage extends StatefulWidget {
  const ClockPage({super.key});

  @override
  State<ClockPage> createState() => _ClockPageState();
}

class _ClockPageState extends State<ClockPage> {
  final PicoViewController _controller = PicoViewController();

  PicoLinkState _link = PicoLinkState.disconnected;
  StreamSubscription<PicoLinkState>? _linkSub;

  String? _openError;

  @override
  void initState() {
    super.initState();
    _controller.init();
    _linkSub = _controller.linkStates.listen((state) {
      if (mounted) setState(() => _link = state);
    });
    unawaited(_open());
  }

  Future<void> _open() async {
    try {
      await _controller.open(const PicoViewConfig());
    } on PicoViewException catch (e) {
      if (mounted) setState(() => _openError = e.message);
    }
  }

  @override
  void dispose() {
    _linkSub?.cancel();
    _controller.disposeSync();
    super.dispose();
  }

  String get _status => switch (_link) {
    PicoLinkState.connected =>
      'connected'
          '${_controller.firmwareVersion == null ? '' : ' · fw ${_controller.firmwareVersion}'}',
    PicoLinkState.disconnected =>
      _openError ?? 'no panel attached — mirroring to screen only',
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // The child is laid out at the panel's resolution, so what you see
            // here is pixel-for-pixel what the panel shows.
            PicoView(controller: _controller, child: const ClockFace()),
            const SizedBox(height: 24),
            Text(_status, style: Theme.of(context).textTheme.bodySmall),
          ],
        ),
      ),
    );
  }
}

/// A round clock face sized for the 360x360 panel. Tap anywhere — on-screen or
/// on the panel itself — to switch between the analog and digital face.
class ClockFace extends StatefulWidget {
  const ClockFace({super.key});

  @override
  State<ClockFace> createState() => _ClockFaceState();
}

class _ClockFaceState extends State<ClockFace> {
  late final Timer _timer;
  DateTime _now = DateTime.now();
  bool _digital = false;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(const Duration(milliseconds: 100), (_) {
      if (mounted) setState(() => _now = DateTime.now());
    });
  }

  @override
  void dispose() {
    _timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _digital = !_digital),
      child: Container(
        decoration: const BoxDecoration(
          shape: BoxShape.circle,
          color: Color(0xFF101418),
        ),
        child: Center(
          child: _digital
              ? Text(
                  '${_two(_now.hour)}:${_two(_now.minute)}:${_two(_now.second)}',
                  style: const TextStyle(
                    fontSize: 56,
                    fontWeight: FontWeight.w600,
                    fontFeatures: [FontFeature.tabularFigures()],
                    color: Color(0xFFE6EDF3),
                  ),
                )
              : CustomPaint(size: const Size.square(360), painter: _Dial(_now)),
        ),
      ),
    );
  }

  static String _two(int v) => v.toString().padLeft(2, '0');
}

class _Dial extends CustomPainter {
  _Dial(this.now);

  final DateTime now;

  @override
  void paint(Canvas canvas, Size size) {
    final center = size.center(Offset.zero);
    final radius = size.shortestSide / 2;

    final tick = Paint()
      ..color = const Color(0xFF3D444D)
      ..strokeWidth = 2;
    for (var i = 0; i < 60; i++) {
      final angle = i * math.pi / 30;
      final outer = radius - 14;
      final inner = outer - (i % 5 == 0 ? 14.0 : 6.0);
      final dir = Offset(math.sin(angle), -math.cos(angle));
      canvas.drawLine(center + dir * inner, center + dir * outer, tick);
    }

    void hand(double turns, double length, double width, Color color) {
      final angle = turns * 2 * math.pi;
      final dir = Offset(math.sin(angle), -math.cos(angle));
      canvas.drawLine(
        center - dir * 20,
        center + dir * length,
        Paint()
          ..color = color
          ..strokeWidth = width
          ..strokeCap = StrokeCap.round,
      );
    }

    final seconds = now.second + now.millisecond / 1000;
    final minutes = now.minute + seconds / 60;
    final hours = now.hour % 12 + minutes / 60;

    hand(hours / 12, radius * 0.5, 9, const Color(0xFFE6EDF3));
    hand(minutes / 60, radius * 0.72, 6, const Color(0xFFE6EDF3));
    hand(seconds / 60, radius * 0.78, 2, const Color(0xFFF85149));

    canvas.drawCircle(center, 6, Paint()..color = const Color(0xFFF85149));
  }

  @override
  bool shouldRepaint(_Dial old) => old.now != now;
}
0
likes
155
points
11
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Mirror a Flutter widget subtree to an external LCD driven by ESP32-P4 firmware over USB, with capacitive touch fed back into the widget tree.

Repository (GitHub)
View/report issues

Topics

#ffi #esp32 #display #embedded

License

Apache-2.0 (license)

Dependencies

code_assets, crypto, ffi, fixnum, flutter, hooks, protobuf

More

Packages that depend on pico_view