Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add advanced button component #2742

Merged
merged 65 commits into from Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
3ae9951
add advanced button component
denisgl7 Sep 18, 2023
6af0a43
add advanced button component
denisgl7 Sep 18, 2023
405ef55
problems fixed
denisgl7 Sep 18, 2023
1e871ff
problems fixed
denisgl7 Sep 18, 2023
d3b8357
melos format
denisgl7 Sep 18, 2023
027299d
Merge branch 'main' into add_advanced_button_component
denisgl7 Sep 18, 2023
2a66b0c
add advanced button test
denisgl7 Sep 18, 2023
3157560
add advanced button test
denisgl7 Sep 18, 2023
efa67d6
Merge remote-tracking branch 'origin/add_advanced_button_component' i…
denisgl7 Sep 18, 2023
a3d18e0
add docs
denisgl7 Sep 19, 2023
ba2547f
Apply suggestions from code review
spydon Sep 21, 2023
624b60b
Merge branch 'main' into add_advanced_button_component
spydon Sep 21, 2023
57c802a
Update doc/flame/inputs/other_inputs.md
spydon Sep 21, 2023
a51b83c
Fix analyze
spydon Sep 21, 2023
fa07fa9
Merge branch 'main' into add_advanced_button_component
spydon Sep 21, 2023
f0a8639
add docs
denisgl7 Sep 23, 2023
bc0f2ee
add docs
denisgl7 Sep 23, 2023
d1bda4b
add docs
denisgl7 Sep 23, 2023
5d499d0
Merge branch 'flame-engine:main' into add_advanced_button_component
denisgl7 Sep 24, 2023
b7f7e44
add docs
denisgl7 Sep 24, 2023
71ee1a9
Example and test created
denisgl7 Sep 24, 2023
e7642f0
Example and test created
denisgl7 Sep 24, 2023
8f8f8f0
rename onChange to onChangeSelected
denisgl7 Sep 24, 2023
77f89ff
rename onChange to onChangeSelected
denisgl7 Sep 24, 2023
586f848
rename onChange to onChangeSelected
denisgl7 Sep 24, 2023
b300928
rename onSelected to onSelectedChanged
denisgl7 Sep 24, 2023
84f800a
rename onSelected to onSelectedChanged
denisgl7 Sep 25, 2023
0aed8b6
rename onSelected to onSelectedChanged
denisgl7 Sep 25, 2023
3472d6a
refactor doc
denisgl7 Sep 25, 2023
43dd266
refactor doc
denisgl7 Sep 25, 2023
853edf0
Merge branch 'flame-engine:main' into add_advanced_button_component
denisgl7 Sep 25, 2023
5a95a83
add labels
denisgl7 Sep 26, 2023
43d179c
removed unnecessary fields
denisgl7 Sep 26, 2023
ed67e0c
removed unnecessary fields
denisgl7 Sep 26, 2023
1d54b72
Merge branch 'flame-engine:main' into add_advanced_button_component
denisgl7 Oct 9, 2023
d8ad10e
Update doc/flame/inputs/other_inputs.md
denisgl7 Oct 9, 2023
50fbdc8
Update doc/flame/inputs/other_inputs.md
denisgl7 Oct 9, 2023
20d5bdd
Update packages/flame/lib/src/components/input/toggle_button_componen…
denisgl7 Oct 9, 2023
e7d7389
Update doc/flame/inputs/other_inputs.md
denisgl7 Oct 9, 2023
7d54944
Update packages/flame/lib/src/components/input/toggle_button_componen…
denisgl7 Oct 9, 2023
edaca7b
Update packages/flame/lib/src/components/input/toggle_button_componen…
denisgl7 Oct 9, 2023
4f1384b
Update packages/flame/lib/src/components/input/toggle_button_componen…
denisgl7 Oct 9, 2023
107bd0c
Update examples/lib/stories/input/advanced_button_example.dart
denisgl7 Oct 9, 2023
955c455
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
2e50637
Update doc/flame/inputs/other_inputs.md
denisgl7 Oct 9, 2023
ae0aa20
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
a2e9c53
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
072dc48
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
95f2e69
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
f667223
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
f768629
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
0391a5d
Update packages/flame/lib/src/components/input/advanced_button_compon…
denisgl7 Oct 9, 2023
a54576a
fixed
denisgl7 Oct 9, 2023
ed072f4
fixed
denisgl7 Oct 9, 2023
de8035d
fixed
denisgl7 Oct 9, 2023
4c7b8a8
add golden test to advanced button
denisgl7 Oct 9, 2023
d1391d2
add golden test to advanced button
denisgl7 Oct 9, 2023
16a47b5
add golden test to advanced button
denisgl7 Oct 9, 2023
0120128
add golden test to advanced button
denisgl7 Oct 9, 2023
6fa7283
add golden test to advanced button
denisgl7 Oct 9, 2023
7ef1b10
add golden test to advanced button
denisgl7 Oct 9, 2023
e4b79b3
reduce size in advanced button golden test
denisgl7 Oct 9, 2023
fde43a6
add golden test to advanced button
denisgl7 Oct 10, 2023
2bfa8fd
Fix golden
spydon Oct 10, 2023
cd782fa
Merge branch 'main' into add_advanced_button_component
spydon Oct 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/flame/inputs/other_inputs.md
Expand Up @@ -142,3 +142,24 @@ else which isn't a pure sprite.

Flame has a separate plugin to support external game controllers (gamepads), check
[here](https://github.com/flame-engine/flame_gamepad) for more information.


## AdvancedButtonComponent

The `AdvancedButtonComponent` have separate states for each of the different pointer phases.
The skin can be customized for each state and each skin is represented by a `PositionComponent`.

These are the fields that should be used to know the state of the `AdvancedButtonComponent`:
denisgl7 marked this conversation as resolved.
Show resolved Hide resolved

- `defaultSkin`: Component that will be displayed by default on the button.
- `downSkin`: Component displayed when the button is clicked or tapped.
- `hoverSkin`: Component displayed when the button is hovered. (desktop and web).
- `defaultSelectedSkin`: The component to display when the button is selected.
The button property must be IsSelectable = true. In this case, the button will have two states
to toggle between (imagine a switch component).
- `downAndSelectedSkin`: The component that is displayed when the selectable button is selected and
pressed.
- `hoverAndSelectedSkin`: Hover on selectable and selected button (desktop and web).

The component also supports two skins, `disabledSkin` and `disabledAndSelectedSkin`, for the disabled
state. This state is achieved by using `setDisabled`.
spydon marked this conversation as resolved.
Show resolved Hide resolved
270 changes: 270 additions & 0 deletions packages/flame/lib/src/components/input/advanced_button_component.dart
@@ -0,0 +1,270 @@
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flutter/foundation.dart';

class AdvancedButtonComponent extends PositionComponent
denisgl7 marked this conversation as resolved.
Show resolved Hide resolved
with HoverCallbacks, TapCallbacks {
AdvancedButtonComponent({
PositionComponent? defaultSkin,
PositionComponent? downSkin,
PositionComponent? hoverSkin,
this.onPressed,
bool isSelectable = false,
spydon marked this conversation as resolved.
Show resolved Hide resolved
spydon marked this conversation as resolved.
Show resolved Hide resolved
PositionComponent? defaultSelectedSkin,
PositionComponent? downAndSelectedSkin,
PositionComponent? hoverAndSelectedSkin,
PositionComponent? disabledSkin,
PositionComponent? disabledAndSelectedSkin,
super.size,
super.position,
super.scale,
super.angle,
super.anchor,
super.children,
super.priority,
}) {
size.addListener(_updateSizes);
this.defaultSkin = defaultSkin;
this.downSkin = downSkin;
this.hoverSkin = hoverSkin;
this.defaultSelectedSkin = defaultSelectedSkin;
this.downAndSelectedSkin = downAndSelectedSkin;
this.hoverAndSelectedSkin = hoverAndSelectedSkin;
this.disabledSkin = disabledSkin;
this.disabledAndSelectedSkin = disabledAndSelectedSkin;
setIsSelectable(isSelectable: isSelectable);
}

@override
@mustCallSuper
void onMount() {
super.onMount();
assert(
defaultSkin != null,
'The defaultSkin has to either be passed '
'in as an argument or set in onLoad',
);
if (_state.isDefault && !contains(defaultSkin!)) {
add(defaultSkin!);
}
}

bool _isPressed = false;

@override
@mustCallSuper
void onTapDown(TapDownEvent event) {
if (_isDisabled) {
return;
}
onPressed?.call();
_isPressed = true;
_updateState();
}

void Function()? onPressed;

@override
void onTapUp(TapUpEvent event) {
_isPressed = false;
if (_isSelectable) {
setSelected(isSelected: !_isSelected);
return;
}
_updateState();
}

bool _isHovered = false;

@override
void onHoverEnter() {
_isHovered = true;
_updateState();
}

@override
void onHoverExit() {
_isHovered = false;
_isPressed = false;
_updateState();
}

Map<ButtonState, PositionComponent?> skinsMap = {};

PositionComponent? get defaultSkin => skinsMap[ButtonState.up];

set defaultSkin(PositionComponent? value) {
skinsMap[ButtonState.up] = value;
if (size.isZero()) {
size = skinsMap[ButtonState.up]?.size ?? Vector2.zero();
}
_invalidateSkins();
}

set downSkin(PositionComponent? value) {
skinsMap[ButtonState.down] = value;
_invalidateSkins();
}

set defaultSelectedSkin(PositionComponent? value) {
skinsMap[ButtonState.upAndSelected] = value;
_invalidateSkins();
}

set downAndSelectedSkin(PositionComponent? value) {
skinsMap[ButtonState.downAndSelected] = value;
_invalidateSkins();
}

set hoverSkin(PositionComponent? value) {
skinsMap[ButtonState.hover] = value;
_invalidateSkins();
}

set hoverAndSelectedSkin(PositionComponent? value) {
skinsMap[ButtonState.hoverAndSelected] = value;
_invalidateSkins();
}

set disabledSkin(PositionComponent? value) {
skinsMap[ButtonState.disabled] = value;
_invalidateSkins();
}

set disabledAndSelectedSkin(PositionComponent? value) {
skinsMap[ButtonState.disabledAndSelected] = value;
_invalidateSkins();
}

void _invalidateSkins() {
_updateSizes();
_updateState();
}

bool _isDisabled = false;

bool get isDisabled => _isDisabled;

void setDisabled({required bool isDisabled}) {
if (_isDisabled == isDisabled) {
return;
}
_isDisabled = isDisabled;
_updateState();
}

bool _isSelectable = false;

bool get isSelectable => _isSelectable;

void setIsSelectable({required bool isSelectable}) {
if (_isSelectable == isSelectable) {
return;
}
_isSelectable = isSelectable;
_updateState();
}

bool _isSelected = false;

bool get isSelected => _isSelected;

void setSelected({required bool isSelected}) {
if (_isSelected == isSelected) {
return;
}
_isSelected = isSelected;
_updateState();
}

void _updateSizes() {
for (final skin in skinsMap.values) {
skin?.size = size;
}
}

void _updateState() {
final isSelectableAndSelected = _isSelectable && _isSelected;
if (_isDisabled) {
_setState(
isSelectableAndSelected &&
_hasSkinForState(ButtonState.disabledAndSelected)
? ButtonState.disabledAndSelected
: ButtonState.disabled,
);
return;
}
if (_isPressed) {
_setState(
isSelectableAndSelected && _hasSkinForState(ButtonState.downAndSelected)
? ButtonState.downAndSelected
: ButtonState.down,
);
return;
}
if (_isHovered) {
final hoverState = isSelectableAndSelected
? ButtonState.hoverAndSelected
: ButtonState.hover;
if (_hasSkinForState(hoverState)) {
_setState(hoverState);
return;
}
}
_setState(
isSelectableAndSelected && _hasSkinForState(ButtonState.upAndSelected)
? ButtonState.upAndSelected
: ButtonState.up,
);
}

ButtonState _state = ButtonState.up;

void _setState(ButtonState value) {
if (_state == value) {
return;
}
_state = value;
_removeSkins();
_addSkin(_state);
}

void _addSkin(ButtonState state) {
(skinsMap[state] ?? defaultSkin)?.parent = this;
}

void _removeSkins() {
for (final state in ButtonState.values) {
if (state != _state) {
_removeSkin(state);
}
}
}

void _removeSkin(ButtonState state) {
if (skinsMap[state]?.parent != null) {
skinsMap[state]?.removeFromParent();
}
}

bool _hasSkinForState(ButtonState state) {
return skinsMap[state] != null;
}
}

enum ButtonState {
up,
upAndSelected,
down,
downAndSelected,
hover,
hoverAndSelected,
disabled,
disabledAndSelected;

const ButtonState();

bool get isDefault {
denisgl7 marked this conversation as resolved.
Show resolved Hide resolved
return this == ButtonState.up;
}
}