Skip to content

Commit

Permalink
feat: dpad (#153)
Browse files Browse the repository at this point in the history
* feat: adding dpad

* feat: adding example and a simple test
  • Loading branch information
erickzanardo authored Feb 3, 2025
1 parent ae11c27 commit 4c631f2
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 0.22.0
- feat: adding `NesIcons.shovel`.
- feat: adding `NesDpad`

# 0.21.0
- Update to Flutter 3.22.0
Expand Down
2 changes: 2 additions & 0 deletions example/lib/gallery/gallery_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class GalleryPage extends StatelessWidget {
const SizedBox(height: 32),
const RunningTextSection(),
const SizedBox(height: 32),
const DpadsSection(),
const SizedBox(height: 32),
const EffectsSection(),
const SizedBox(height: 32),
const LoadingIndicatorsSection(),
Expand Down
60 changes: 60 additions & 0 deletions example/lib/gallery/sections/dpads.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:nes_ui/nes_ui.dart';

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

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Dpads',
style: theme.textTheme.displayMedium,
),
const SizedBox(height: 16),
const ControllingDpad(),
],
);
}
}

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

@override
State createState() => _ControllingDpadState();
}

class _ControllingDpadState extends State<ControllingDpad> {
NesDpadDirection? _direction;

@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NesDpad(
onButtonDown: (direction) {
setState(() {
_direction = direction;
});
},
onButtonUp: (direction) {
setState(() {
_direction = null;
});
},
),
const SizedBox(height: 16),
Text(
'Direction: ${_direction?.name ?? 'none'}',
),
],
),
);
}
}
1 change: 1 addition & 0 deletions example/lib/gallery/sections/sections.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export 'checkboxes.dart';
export 'containers.dart';
export 'custom_extensions.dart';
export 'dialogs.dart';
export 'dpads.dart';
export 'drop_shadows.dart';
export 'dropdown_menus.dart';
export 'effects.dart';
Expand Down
198 changes: 198 additions & 0 deletions lib/src/widgets/nes_dpad.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:nes_ui/nes_ui.dart';

/// The four ways direction of the NES Dpad.
enum NesDpadDirection {
/// Up
up,

/// Down
down,

/// Left
left,

/// Right
right,
}

/// {@template nes_dpad}
/// A widget that renders a four ways directional pad.
/// {@endtemplate}
class NesDpad extends StatefulWidget {
/// {@macro nes_dpad}
const NesDpad({
this.onButtonDown,
this.onButtonUp,
this.buttonBuilder = _defaultButtonBuilder,
this.buttonSize = 40,
super.key,
});

/// The callback that is called when a direction is pressed.
final void Function(NesDpadDirection)? onButtonDown;

/// The callback that is called when a direction is released.
final void Function(NesDpadDirection)? onButtonUp;

/// The builder that is called to render the key.
final Widget Function(
BuildContext context,
NesDpadDirection direction, {
required double buttonSize,
required bool pressed,
}) buttonBuilder;

/// The size of the button.
final double buttonSize;

@override
State<NesDpad> createState() => _NesDpadState();
}

class _NesDpadState extends State<NesDpad> {
bool _upPressed = false;
bool _downPressed = false;
bool _leftPressed = false;
bool _rightPressed = false;

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: widget.buttonSize),
GestureDetector(
child: widget.buttonBuilder(
context,
NesDpadDirection.up,
buttonSize: widget.buttonSize,
pressed: _upPressed,
),
onTapDown: (_) {
widget.onButtonDown?.call(NesDpadDirection.up);
setState(() {
_upPressed = true;
});
},
onTapUp: (_) {
widget.onButtonUp?.call(NesDpadDirection.up);
setState(() {
_upPressed = false;
});
},
),
SizedBox(width: widget.buttonSize),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
child: widget.buttonBuilder(
context,
NesDpadDirection.left,
buttonSize: widget.buttonSize,
pressed: _leftPressed,
),
onTapDown: (_) {
widget.onButtonDown?.call(NesDpadDirection.left);
setState(() {
_leftPressed = true;
});
},
onTapUp: (_) {
widget.onButtonUp?.call(NesDpadDirection.left);
setState(() {
_leftPressed = false;
});
},
),
SizedBox(width: widget.buttonSize),
GestureDetector(
child: widget.buttonBuilder(
context,
NesDpadDirection.right,
buttonSize: widget.buttonSize,
pressed: _rightPressed,
),
onTapDown: (_) {
widget.onButtonDown?.call(NesDpadDirection.right);
setState(() {
_rightPressed = true;
});
},
onTapUp: (_) {
widget.onButtonUp?.call(NesDpadDirection.right);
setState(() {
_rightPressed = false;
});
},
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: widget.buttonSize),
GestureDetector(
child: widget.buttonBuilder(
context,
NesDpadDirection.down,
buttonSize: widget.buttonSize,
pressed: _downPressed,
),
onTapDown: (_) {
widget.onButtonDown?.call(NesDpadDirection.down);
setState(() {
_downPressed = true;
});
},
onTapUp: (_) {
widget.onButtonUp?.call(NesDpadDirection.down);
setState(() {
_downPressed = false;
});
},
),
SizedBox(width: widget.buttonSize),
],
),
],
);
}
}

Widget _defaultButtonBuilder(
BuildContext context,
NesDpadDirection direction, {
required double buttonSize,
required bool pressed,
}) {
late final NesIconData iconData;

switch (direction) {
case NesDpadDirection.up:
iconData = NesIcons.topArrowIndicator;
case NesDpadDirection.down:
iconData = NesIcons.bottomArrowIndicator;
case NesDpadDirection.left:
iconData = NesIcons.leftArrowIndicator;
case NesDpadDirection.right:
iconData = NesIcons.rightArrowIndicator;
}

return SizedBox.square(
dimension: buttonSize,
child: Transform.translate(
offset: pressed ? const Offset(0, 4) : Offset.zero,
child: NesIcon(
iconData: iconData,
size: Size.square(buttonSize),
),
),
);
}
1 change: 1 addition & 0 deletions lib/src/widgets/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export 'nes_button.dart';
export 'nes_check_box.dart';
export 'nes_checkered_decoration.dart';
export 'nes_controller_focus.dart';
export 'nes_dpad.dart';
export 'nes_dropdown_menu.dart';
export 'nes_dropshadow.dart';
export 'nes_file_explorer.dart';
Expand Down
22 changes: 22 additions & 0 deletions test/src/widgets/nes_dpad_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ignore_for_file: prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:nes_ui/nes_ui.dart';

void main() {
group('NesContainer', () {
testWidgets('renders its child', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: flutterNesTheme(),
home: const NesDpad(),
),
);

expect(find.byType(NesDpad), findsOneWidget);
// By default the dpad has 4 icons
expect(find.byType(NesIcon), findsNWidgets(4));
});
});
}
63 changes: 63 additions & 0 deletions widgetbook/lib/widgetbook/use_cases/dpads.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// ignore_for_file: public_member_api_docs

import 'package:flutter/widgets.dart';
import 'package:nes_ui/nes_ui.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

@widgetbook.UseCase(
name: 'default',
type: NesDpad,
)
Widget normal(BuildContext context) {
return const Center(
child: NesDpad(),
);
}

@widgetbook.UseCase(
name: 'with callbacks',
type: NesDpad,
)
Widget withCallbacks(BuildContext context) {
return const Center(
child: ControllingDpad(),
);
}

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

@override
State createState() => _ControllingDpadState();
}

class _ControllingDpadState extends State<ControllingDpad> {
NesDpadDirection? _direction;

@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NesDpad(
onButtonDown: (direction) {
setState(() {
_direction = direction;
});
},
onButtonUp: (direction) {
setState(() {
_direction = null;
});
},
),
const SizedBox(height: 16),
Text(
'Direction: ${_direction?.name ?? 'none'}',
),
],
),
);
}
}
Loading

0 comments on commit 4c631f2

Please sign in to comment.