diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f7448..847eb91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ - feat: add `NesIcons.redo` - feat: add `customExtensions` - feat: add keyboard support +- feat: add `NesIcons.unpressedButton` +- feat: add `NesIcons.pressedButton` +- feat: add `NesKeyButton` # 0.1.0 diff --git a/example/lib/gallery/gallery_page.dart b/example/lib/gallery/gallery_page.dart index f5d0c97..60c6ca4 100644 --- a/example/lib/gallery/gallery_page.dart +++ b/example/lib/gallery/gallery_page.dart @@ -35,6 +35,8 @@ class GalleryPage extends StatelessWidget { const SizedBox(height: 32), const TextSection(), const SizedBox(height: 32), + const KeyIconsSection(), + const SizedBox(height: 32), const SelectionListSection(), const SizedBox(height: 32), const CheckBoxesSection(), diff --git a/example/lib/gallery/sections/key_icons.dart b/example/lib/gallery/sections/key_icons.dart new file mode 100644 index 0000000..1d85538 --- /dev/null +++ b/example/lib/gallery/sections/key_icons.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:nes_ui/nes_ui.dart'; +import 'package:phased/phased.dart'; + +class KeyIconsSection extends StatelessWidget { + const KeyIconsSection({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Key Icons', + style: theme.textTheme.displayMedium, + ), + const SizedBox(height: 16), + Wrap( + children: [ + const NesKeyIcon( + buttonKey: 'a', + ), + const SizedBox(width: 16), + const NesKeyIcon( + buttonKey: 'a', + pressed: true, + ), + const SizedBox(width: 16), + _Toogler(), + ], + ), + ], + ); + } +} + +class _Toogler extends Phased { + _Toogler() + : super( + state: PhasedState( + values: [true, false], + ticker: const Duration(milliseconds: 500), + ), + ); + + @override + Widget build(BuildContext context) { + return NesKeyIcon( + buttonKey: 'a', + pressed: state.value, + ); + } +} diff --git a/example/lib/gallery/sections/sections.dart b/example/lib/gallery/sections/sections.dart index 220ad3f..e673068 100644 --- a/example/lib/gallery/sections/sections.dart +++ b/example/lib/gallery/sections/sections.dart @@ -3,6 +3,7 @@ export 'checkboxes.dart'; export 'containers.dart'; export 'custom_extensions.dart'; export 'icons.dart'; +export 'key_icons.dart'; export 'selection_list.dart'; export 'text_fields.dart'; export 'texts.dart'; diff --git a/example/pubspec.lock b/example/pubspec.lock index 3d7b5c6..6d4da36 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -214,7 +214,7 @@ packages: source: hosted version: "2.1.3" phased: - dependency: transitive + dependency: "direct main" description: name: phased sha256: "4dc19d589fd059268b07357767ac96eef2861eb5e8ceedf9d23fb933f0128396" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9581aae..388faa2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: flutter_bloc: ^8.1.1 nes_ui: path: ../ + phased: ^0.0.3 dev_dependencies: very_good_analysis: ^3.1.0 diff --git a/lib/src/widgets/nes_icon.dart b/lib/src/widgets/nes_icon.dart index fb617ea..1b6e922 100644 --- a/lib/src/widgets/nes_icon.dart +++ b/lib/src/widgets/nes_icon.dart @@ -142,6 +142,20 @@ class NesIcons { '8,8;2,-1;4,0;3,-1;1,0;4,-1;1,0;1,-1;1,0;6,-1;1,0;2,-1;1,0;4,-1;1,0;1,-1;1,0;1,-1;1,0;3,-1;2,0;1,-1;1,0;1,-1;1,0;2,-1;1,0;2,-1;1,0;3,-1;1,0;4,-1;3,0;2,-1', ), ); + + /// An unpressed button icon. + late final unpressedButton = NesIconData( + MiniSprite.fromDataString( + '8,8;2,-1;4,0;3,-1;6,0;1,-1;25,0;1,1;4,0;1,1;1,0;1,-1;1,0;4,1;1,0;3,-1;4,0;2,-1', + ), + ); + + /// A pressed button icon. + late final pressedButton = NesIconData( + MiniSprite.fromDataString( + '8,8;10,-1;4,0;3,-1;6,0;1,-1;24,0;1,-1;6,0;3,-1;4,0;2,-1', + ), + ); } /// {@template nes_icon} diff --git a/lib/src/widgets/nes_key_icon.dart b/lib/src/widgets/nes_key_icon.dart new file mode 100644 index 0000000..d4c6fc3 --- /dev/null +++ b/lib/src/widgets/nes_key_icon.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:nes_ui/nes_ui.dart'; + +/// {@template nes_key_icon} +/// A pressable icon, representing a single key. +/// +/// This widget by itself doesn't handle touch/key events +/// use it in combination with other widgets to make it +/// interactive. +/// {@endtemplate} +class NesKeyIcon extends StatelessWidget { + /// {@macro nes_key_icon} + const NesKeyIcon({ + super.key, + required this.buttonKey, + this.pressed = false, + this.size, + }) : assert(buttonKey.length == 1, 'buttonKey must be a single character'); + + /// The key that this icon represents + final String buttonKey; + + /// If this button is in a pressed state or not. + final bool pressed; + + /// Size. + final Size? size; + + @override + Widget build(BuildContext context) { + final nesTheme = context.nesThemeExtension(); + final nesIconTheme = context.nesThemeExtension(); + + final pixelSize = nesTheme.pixelSize.toDouble(); + + final iconData = pressed + ? NesIcons.instance.pressedButton + : NesIcons.instance.unpressedButton; + + final buttonSize = size ?? + Size( + iconData.sprite.pixels[0].length * pixelSize, + iconData.sprite.pixels.length * pixelSize, + ); + + return Stack( + children: [ + Positioned( + child: NesIcon( + size: buttonSize, + iconData: pressed + ? NesIcons.instance.pressedButton + : NesIcons.instance.unpressedButton, + ), + ), + Positioned( + left: buttonSize.width / 2 - 5, + top: buttonSize.height / 2 - (pressed ? 4 : 8), + child: Text( + buttonKey.toUpperCase(), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: nesIconTheme.secondary, + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/widgets/widgets.dart b/lib/src/widgets/widgets.dart index 661a549..b02ddb6 100644 --- a/lib/src/widgets/widgets.dart +++ b/lib/src/widgets/widgets.dart @@ -4,4 +4,5 @@ export 'nes_container.dart'; export 'nes_controller_focus.dart'; export 'nes_icon.dart'; export 'nes_input.dart'; +export 'nes_key_icon.dart'; export 'nes_selection_list.dart'; diff --git a/test/src/widgets/nes_key_icon_test.dart b/test/src/widgets/nes_key_icon_test.dart new file mode 100644 index 0000000..55f7e91 --- /dev/null +++ b/test/src/widgets/nes_key_icon_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nes_ui/nes_ui.dart'; + +void main() { + group('NesIcon', () { + testWidgets('renders correctly', (tester) async { + await tester.pumpWidget( + MaterialApp( + theme: flutterNesTheme(), + home: const Scaffold( + body: NesKeyIcon( + buttonKey: 'A', + ), + ), + ), + ); + + expect(find.byType(NesKeyIcon), findsOneWidget); + }); + + testWidgets('renders pressed correctly', (tester) async { + await tester.pumpWidget( + MaterialApp( + theme: flutterNesTheme(), + home: const Scaffold( + body: NesKeyIcon( + buttonKey: 'A', + pressed: true, + ), + ), + ), + ); + + expect(find.byType(NesKeyIcon), findsOneWidget); + }); + }); +}