pixel_ui 0.5.1 copy "pixel_ui: ^0.5.1" to clipboard
pixel_ui: ^0.5.1 copied to clipboard

Pixel-art design system for Flutter — retro / 8-bit primitives, tile grids, interactive buttons, and a bundled pixel font for game UI.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'pixel_ui showcase',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        scaffoldBackgroundColor: const Color(0xFFF5F1E8),
        textTheme: Typography.blackMountainView.apply(
          fontFamily: PixelText.mulmaruFontFamily,
          package: PixelText.mulmaruPackage,
        ),
      ),
      home: const _ShowcaseScreen(),
    );
  }
}

class _ShowcaseScreen extends StatelessWidget {
  const _ShowcaseScreen();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('pixel_ui', style: PixelText.mulmaru(fontSize: 20)),
        backgroundColor: const Color(0xFFE8DFC6),
        elevation: 0,
      ),
      body: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 480),
          child: ListView(
            padding: const EdgeInsets.symmetric(vertical: 24),
            children: [
              const _HeroComposition(),
              const _SectionDivider('1. Corners scale'),
              const _CornersShowcase(),
              const _SectionDivider('2. Shadows'),
              const _ShadowsShowcase(),
              const _SectionDivider('3. Buttons'),
              const _ButtonsShowcase(),
              const _SectionDivider('4. Texture'),
              const _TextureShowcase(),
              const _SectionDivider('5. Theme inheritance'),
              const _ThemeShowcase(),
              const _SectionDivider('6. Label carve-out'),
              const _LabelShowcase(),
              const _SectionDivider('7. PixelGrid — inventory'),
              const Padding(
                padding: EdgeInsets.symmetric(horizontal: 24),
                child: _PixelGridDemo(),
              ),
              const SizedBox(height: 40),
            ],
          ),
        ),
      ),
    );
  }
}

class _SectionDivider extends StatelessWidget {
  final String title;
  const _SectionDivider(this.title);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(24, 32, 24, 16),
      child: Text(title, style: PixelText.mulmaru(fontSize: 18)),
    );
  }
}

/// Section 1 — all corner scales side by side.
class _CornersShowcase extends StatelessWidget {
  const _CornersShowcase();

  @override
  Widget build(BuildContext context) {
    const items = <(String, PixelCorners)>[
      ('sharp', PixelCorners.sharp),
      ('xs', PixelCorners.xs),
      ('sm', PixelCorners.sm),
      ('md', PixelCorners.md),
      ('lg', PixelCorners.lg),
      ('xl', PixelCorners.xl),
    ];
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Wrap(
        spacing: 16,
        runSpacing: 16,
        children: [
          for (final (label, corners) in items)
            _Labelled(
              label,
              PixelBox(
                logicalWidth: 16,
                logicalHeight: 16,
                style: PixelShapeStyle(
                  corners: corners,
                  fillColor: const Color(0xFF5A8A3A),
                  borderColor: const Color(0xFF2A4820),
                  borderWidth: 1,
                ),
              ),
            ),
          _Labelled(
            'topTab',
            PixelBox(
              logicalWidth: 32,
              logicalHeight: 16,
              style: const PixelShapeStyle(
                corners: PixelCorners.only(tl: [3, 2, 1], tr: [3, 2, 1]),
                fillColor: Color(0xFFE07A3C),
                borderColor: Color(0xFF8B3E1A),
                borderWidth: 1,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Section 2 — sm/md/lg shadow scale.
class _ShadowsShowcase extends StatelessWidget {
  const _ShadowsShowcase();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Row(
        children: [
          _Labelled(
            'sm',
            PixelBox(
              logicalWidth: 16,
              logicalHeight: 16,
              style: PixelShapeStyle(
                corners: PixelCorners.md,
                fillColor: const Color(0xFFFFD643),
                shadow: PixelShadow.sm(const Color(0xFF8A5A10)),
              ),
            ),
          ),
          const SizedBox(width: 24),
          _Labelled(
            'md',
            PixelBox(
              logicalWidth: 16,
              logicalHeight: 16,
              style: PixelShapeStyle(
                corners: PixelCorners.md,
                fillColor: const Color(0xFFFFD643),
                shadow: PixelShadow.md(const Color(0xFF8A5A10)),
              ),
            ),
          ),
          const SizedBox(width: 24),
          _Labelled(
            'lg',
            PixelBox(
              logicalWidth: 16,
              logicalHeight: 16,
              style: PixelShapeStyle(
                corners: PixelCorners.md,
                fillColor: const Color(0xFFFFD643),
                shadow: PixelShadow.lg(const Color(0xFF8A5A10)),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Section 3 — interactive button states.
class _ButtonsShowcase extends StatefulWidget {
  const _ButtonsShowcase();

  @override
  State<_ButtonsShowcase> createState() => _ButtonsShowcaseState();
}

class _ButtonsShowcaseState extends State<_ButtonsShowcase> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    const baseNormal = PixelShapeStyle(
      corners: PixelCorners.lg,
      fillColor: Color(0xFF5A8A3A),
      borderColor: Color(0xFF2A4820),
      borderWidth: 1,
      shadow: PixelShadow(offset: Offset(1, 1), color: Color(0xFF1A3010)),
    );
    const basePressed = PixelShapeStyle(
      corners: PixelCorners.lg,
      fillColor: Color(0xFF3E6028),
      borderColor: Color(0xFF1A3010),
      borderWidth: 1,
    );

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Pressed count: $_count', style: PixelText.mulmaru(fontSize: 14)),
          const SizedBox(height: 12),
          PixelButton(
            logicalWidth: 60,
            logicalHeight: 18,
            width: 240,
            normalStyle: baseNormal,
            pressedStyle: basePressed,
            pressChildOffset: const Offset(0, 1),
            onPressed: () => setState(() => _count++),
            semanticsLabel: 'Tap counter',
            child: Text(
              'TAP ME',
              style: PixelText.mulmaru(
                fontSize: 18,
                color: Colors.white,
                shadowColor: const Color(0xFF1A3010),
              ),
            ),
          ),
          const SizedBox(height: 12),
          PixelButton(
            logicalWidth: 60,
            logicalHeight: 18,
            width: 240,
            normalStyle: baseNormal,
            onPressed: null,
            child: Text(
              'DISABLED',
              style: PixelText.mulmaru(
                fontSize: 18,
                color: Colors.white,
                shadowColor: const Color(0xFF1A3010),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Section 4 — texture vs plain.
class _TextureShowcase extends StatelessWidget {
  const _TextureShowcase();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Row(
        children: [
          _Labelled(
            'plain',
            PixelBox(
              logicalWidth: 20,
              logicalHeight: 20,
              style: const PixelShapeStyle(
                corners: PixelCorners.md,
                fillColor: Color(0xFFFFD643),
                borderColor: Color(0xFF8A5A10),
                borderWidth: 1,
              ),
            ),
          ),
          const SizedBox(width: 24),
          _Labelled(
            'textured',
            PixelBox(
              logicalWidth: 20,
              logicalHeight: 20,
              style: const PixelShapeStyle(
                corners: PixelCorners.md,
                fillColor: Color(0xFFFFD643),
                borderColor: Color(0xFF8A5A10),
                borderWidth: 1,
                texture: PixelTexture(
                  color: Color(0xFFFFF7D0),
                  density: 0.15,
                  size: 1,
                  seed: 7,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Section 5 — theme inheritance via `pixelUiTheme`.
class _ThemeShowcase extends StatelessWidget {
  const _ThemeShowcase();

  @override
  Widget build(BuildContext context) {
    const themedStyle = PixelShapeStyle(
      corners: PixelCorners.md,
      fillColor: Color(0xFF3C6BE0),
      borderColor: Color(0xFF1A3A80),
      borderWidth: 1,
    );
    const overrideStyle = PixelShapeStyle(
      corners: PixelCorners.md,
      fillColor: Color(0xFFE07A3C),
      borderColor: Color(0xFF8B3E1A),
      borderWidth: 1,
    );

    return Theme(
      data: pixelUiTheme(
        base: Theme.of(context),
        boxTheme: const PixelBoxTheme(style: themedStyle),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 24),
        child: Row(
          children: const [
            _Labelled(
              'inherits theme',
              PixelBox(logicalWidth: 16, logicalHeight: 16),
            ),
            SizedBox(width: 24),
            _Labelled(
              'style prop wins',
              PixelBox(
                logicalWidth: 16,
                logicalHeight: 16,
                style: overrideStyle,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// Section 6 — labeled box demonstrating painter carve-out.
class _LabelShowcase extends StatelessWidget {
  const _LabelShowcase();

  @override
  Widget build(BuildContext context) {
    const boxStyle = PixelShapeStyle(
      corners: PixelCorners.md,
      fillColor: Color(0xFFE8DFC6),
      borderColor: Color(0xFF2A2A2A),
      borderWidth: 1,
    );

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: PixelBox(
        logicalWidth: 80,
        logicalHeight: 40,
        width: 320,
        style: boxStyle,
        label: Container(
          padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
          color: const Color(0xFFF5F1E8),
          child: Text(
            'INVENTORY',
            style: PixelText.mulmaru(
              fontSize: 12,
              color: const Color(0xFF2A2A2A),
            ),
          ),
        ),
        padding: const EdgeInsets.all(16),
        alignment: Alignment.centerLeft,
        child: Text(
          '  - potion × 3\n  - scroll × 1\n  - gold × 42',
          style: PixelText.mulmaru(
            fontSize: 12,
            color: const Color(0xFF2A2A2A),
          ),
        ),
      ),
    );
  }
}

/// Section 7 — hero composition for pub.dev screenshot #1.
class _HeroComposition extends StatelessWidget {
  const _HeroComposition();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24),
      child: PixelBox(
        logicalWidth: 100,
        logicalHeight: 60,
        width: MediaQuery.of(context).size.width - 48,
        style: const PixelShapeStyle(
          corners: PixelCorners.lg,
          fillColor: Color(0xFFE8DFC6),
          borderColor: Color(0xFF2A2A2A),
          borderWidth: 1,
          shadow: PixelShadow(offset: Offset(2, 2), color: Color(0xFF2A2A2A)),
          texture: PixelTexture(
            color: Color(0xFFFFFFFF),
            density: 0.08,
            size: 1,
            seed: 13,
          ),
        ),
        padding: const EdgeInsets.all(20),
        alignment: Alignment.center,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              'PIXEL UI',
              style: PixelText.mulmaru(
                fontSize: 28,
                color: const Color(0xFF2A2A2A),
                shadowColor: const Color(0xFFE07A3C),
                shadowOffset: const Offset(2, 2),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'PRIMITIVES + FONT',
              style: PixelText.mulmaru(
                fontSize: 14,
                color: const Color(0xFF5A5A5A),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _Labelled extends StatelessWidget {
  final String label;
  final Widget child;
  const _Labelled(this.label, this.child);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        child,
        const SizedBox(height: 6),
        Text(label, style: PixelText.mulmaru(fontSize: 12)),
      ],
    );
  }
}

/// Section 7 — PixelGrid inventory demo (5×3, drag-and-drop, keyboard activate).
class _PixelGridDemo extends StatefulWidget {
  const _PixelGridDemo();

  @override
  State<_PixelGridDemo> createState() => _PixelGridDemoState();
}

class _PixelGridDemoState extends State<_PixelGridDemo> {
  final List<List<_Item?>> _grid = [
    [_Item.sword, _Item.potion, null, _Item.gem, _Item.potion],
    [null, _Item.sword, _Item.gem, null, _Item.potion],
    [_Item.gem, null, _Item.potion, _Item.sword, null],
  ];

  @override
  Widget build(BuildContext context) {
    return PixelGrid<_Item>.fromList(
      data: _grid,
      tileLogicalWidth: 10,
      tileLogicalHeight: 10,
      tileScreenSize: const Size(48, 48),
      styleFor: _styleFor,
      emptyStyle: const PixelShapeStyle(
        corners: PixelCorners.sharp,
        fillColor: Color(0xFF2A2733),
        borderColor: Color(0xFF45404F),
        borderWidth: 1,
      ),
      dragDataFor: (x, y) => _grid[y][x],
      onTileAccept: (from, to, data) => setState(() {
        final (fx, fy) = from;
        final (tx, ty) = to;
        final tmp = _grid[ty][tx];
        _grid[ty][tx] = data;
        _grid[fy][fx] = tmp;
      }),
      onTileActivate: (x, y) => ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          duration: const Duration(milliseconds: 600),
          content: Text('Activated ($x,$y): ${_grid[y][x]?.name ?? "empty"}'),
        ),
      ),
      autofocus: true,
    );
  }
}

enum _Item { sword, potion, gem }

PixelShapeStyle _styleFor(_Item item) {
  switch (item) {
    case _Item.sword:
      return const PixelShapeStyle(
        corners: PixelCorners.sm,
        fillColor: Color(0xFFCCCCCC),
        borderColor: Color(0xFF666666),
        borderWidth: 1,
      );
    case _Item.potion:
      return const PixelShapeStyle(
        corners: PixelCorners.sm,
        fillColor: Color(0xFFC0392B),
        borderColor: Color(0xFF6B1E14),
        borderWidth: 1,
      );
    case _Item.gem:
      return const PixelShapeStyle(
        corners: PixelCorners.sm,
        fillColor: Color(0xFF85C1E9),
        borderColor: Color(0xFF2E86C1),
        borderWidth: 1,
      );
  }
}
5
likes
160
points
356
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Pixel-art design system for Flutter — retro / 8-bit primitives, tile grids, interactive buttons, and a bundled pixel font for game UI.

Repository (GitHub)
View/report issues

Topics

#widget #design-system #pixel-art #retro #game-ui

License

MIT (license)

Dependencies

flutter

More

Packages that depend on pixel_ui