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: Draft for new Layout components Row and Column #1971

Closed
wants to merge 30 commits into from

Conversation

rivella50
Copy link
Contributor

@rivella50 rivella50 commented Sep 30, 2022

Description

This is a draft for the planned row and column components for Flame.
At the moment it only covers the horizontal direction class RowComponent.
As soon as we are clear what features these 2 components will have, the class ColumnComponent will be added accordingly.
How to use RowComponent can be seen in issue #1944 .

To be discussed:

  • how shall the layouting routine behave if the children don't have the same anchor type?
  • additional parameters involved?

Checklist

  • The title of my PR starts with a [Conventional Commit] prefix (fix:, feat:, docs: etc).
  • I have followed the [Contributor Guide] when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • [-] I have updated/added relevant examples in examples or docs.
  • No, this PR is not a breaking change.

Related Issues

Closes #1944

@rivella50 rivella50 marked this pull request as draft September 30, 2022 13:49
@st-pasha
Copy link
Contributor

I'm worried about overriding the add() and remove() methods, since we don't really provide a guarantee that these are the only ways to add or remove children components. For example, there are also addAll(), removeAll(), addToParent(), removeFromParent(), changeParent(), adding via the constructor; more methods like these can be added in the future.

I believe a reasonable alternative would be to:

  • not subscribe to size changes at all: it is a very rare operation after all, especially for the kinds of components that get put in rows/columns;
  • make the layout() method public, so that in a rare circumstance that a child does need to change its size, it would simply instruct the parent to re-do the layout manually;
  • create a new lifecycle callback Component.onChildrenChanged() which would be invoked whenever children components are added/removed (<-- this should probably go in a separate PR);
  • do not require that children of LayoutComponent must be PositionComponents -- what if someone wants to add an Effect for example? The approach taken in layoutChildren() is better: simply perform a filter. And even that can be improved: instead of looking for PositionComponents, use PositionProvider / SizeProvider (the PositionComponent satisfies both of these interfaces).

@rivella50
Copy link
Contributor Author

@st-pasha Thanks for the feedback. Makes all sense imo.

@rivella50
Copy link
Contributor Author

@st-pasha Regarding the filter in layoutChildren: You mean filtering like

final list = children.where((element) => element is PositionProvider && element is SizeProvider);

and then casting to one of the interfaces where needed, e.g.

for (final child in list) {
  (child as PositionProvider).position = Vector2(currentPosition.x, currentPosition.y);
  currentPosition.x += (child as SizeProvider).size.x + gap;  // width cannot be used anymore
}

@rivella50
Copy link
Contributor Author

With the merge of PR #1976 the class LayoutComponent could get rid of the overridden methods add and remove and now only has to override the new method onChildrenChanged in order to get notified about any change in a parent's children and therefore know when to relayout them.

@rivella50
Copy link
Contributor Author

@spydon Can we discuss how to proceed with this draft?
I.e. what's missing in functionality in order to be released?
Writing tests won't be a problem.

@spydon spydon requested review from a team and st-pasha October 6, 2022 08:39
Copy link
Member

@erickzanardo erickzanardo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small nits but I think this good for an initial approach.

I have two suggestions though

  • The code between Column and Row components are very similar, I wonder if we could share some methods/logic between them to avoid repetition.
  • I would suggest to move these components to the experimental namespace, that we way we have the freedom to explore this new functionality without fear of breaking changes.

packages/flame/lib/src/components/column_component.dart Outdated Show resolved Hide resolved
packages/flame/lib/src/components/row_component.dart Outdated Show resolved Hide resolved
@rivella50
Copy link
Contributor Author

@erickzanardo Thanks for the feedback. I've refactored the 3 classes as follows:

  • method layoutChildren has been moved up to class LayoutComponent
  • a switch statement is used now
  • all 3 classes have been moved to the experimental namespace

@erickzanardo erickzanardo requested a review from a team October 6, 2022 14:02
@erickzanardo
Copy link
Member

Awesome. From my part. I would say this is ready to receive tests and examples.

Once we have RowComponent and ColumnComponent on main, we can start expanding this, like an ExpandedComponent, a Flex one and so on.

@rivella50
Copy link
Contributor Author

Great, thank you, Erick. Then i will write some tests for it.
I also hope that these components will receive many more extensions.

Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the sizes for the components that are missing I think (and tests for the sizes), otherwise it looks good!

return;
}
final currentPosition = Vector2.zero();
final componentsDimension = list.fold<double>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be set to the PositionComponent's width or height right? And then the one that isn't set here should be the max width/height of the child components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean componentsDimension? This only sums up the width (or height) for all components which will be used to calculate the free space in all spaceXY and the center alignment type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The size of the parent component is checked and handled here:

final dimensionAvailable = size[vectorIndex] != 0.0
? size[vectorIndex] - absoluteTopLeftPosition[vectorIndex]
: gameRef.canvasSize[vectorIndex] -
absoluteTopLeftPosition[vectorIndex];

Copy link
Member

@spydon spydon Oct 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dimensionAvailable should possibly be the parent's size, or it's own size, it shouldn't check anything regarding the canvas size or such.
So either we let the size of the LayoutComponent grow dynamically with the children added to it, or it has a set size from the beginning, but to make it more flutter like I think that it should grow the size itself and then just be constrained by the parent's size.
Because right now the layout component's size will be (0, 0) now since it isn't updated when children are added. The setter of size should probably also be overridden so that it can't be set too if we choose to go with a dynamic size.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But can the parent not also have a zero size?
I had it in mind like this: if the developer defines a size for LayoutComponent then this size will be taken for the available dimension calculation, else it just takes the canvas' width or height for the end edge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you like. It's already implemented though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps let's not overcomplicate this?

In the first version of the LayoutComponent the size will have to be provided explicitly. In the next version (in a separate PR), we will relax that requirement and allow size to be omitted, in which case it will be derived from a parent.

When re-reviewing this I agree, maybe it's a bit overkill to listen to the parent's size.
Then we can scrap isManuallySized too, so that the component only grows to its size, can we restrict the user from changing the size variable from the outside though? Then we only need to do the layout when children are added.

Later we could add a WrapComponent so that components can be organized within a set size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this @rivella50? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spydon Sure, why not. I've already removed setting the size and isManuallySized last weekend.
Just to be clear: Without being able to set the size (parent or LayoutComponent) the alignment types spaceXY don't make sense anymore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without being able to set the size (parent or LayoutComponent) the alignment types spaceXY don't make sense anymore.

Hmm, that's true, a shame to drop that one...
But maybe it is worth dropping it to just have a simple first version?

@@ -1104,6 +1104,21 @@ Check the example app
for details on how to use it.


## LayoutComponent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we won't expose this we should change the docs to be about RowComponent and ColumnComponent instead.

return;
}
final currentPosition = Vector2.zero();
final componentsDimension = list.fold<double>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps let's not overcomplicate this?

In the first version of the LayoutComponent the size will have to be provided explicitly. In the next version (in a separate PR), we will relax that requirement and allow size to be omitted, in which case it will be derived from a parent.

When re-reviewing this I agree, maybe it's a bit overkill to listen to the parent's size.
Then we can scrap isManuallySized too, so that the component only grows to its size, can we restrict the user from changing the size variable from the outside though? Then we only need to do the layout when children are added.

Later we could add a WrapComponent so that components can be organized within a set size.

@rivella50 rivella50 marked this pull request as ready for review October 16, 2022 17:26
final MainAxisAlignment alignment;
bool _allowSetSize = false;

/// gap between components
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// gap between components

@rivella50 rivella50 requested a review from spydon October 29, 2022 13:12
Comment on lines +30 to +31
for (var k = 0; k <= 1; k++) {
group(k == 0 ? 'RowComponent' : 'ColumnComponent', () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this be better as:

for (final name in ['RowComponent', 'ColumnComponent']) {

or, maybe even better, extract the block to a function and call it twice?

group('RowComponent', testLayoutComponent(RowComponent))
group('ColumnComponent', testLayoutComponent(ColumnComponent))

Comment on lines +54 to +56
_allowSetSize = true;
size[vectorIndex] = totalSizeOfComponents;
_allowSetSize = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this variable -- can you just user super.size?

example:

class A {
  double size = 1.0;
}

class B extends A {
  @override
  set size(double size) {
    assert(false, 'Setting the size is currently unsupported.');
  }
  
  void foo() {
    // this.size = 2.0; -> this breaks
    super.size = 2.0;
  }
}

void main() {
  final b = B()..foo();
  print(b.size);
}

/// type.
class RowComponent extends LayoutComponent {
RowComponent({
LayoutComponentAlignment alignment = LayoutComponentAlignment.start,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't you use super.alignment here?

@@ -1104,6 +1104,18 @@ Check the example app
for details on how to use it.


## RowComponent & ColumnComponent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is experimental, should we flag it in the doc, similar to how it is done elsewhere?

@spydon
Copy link
Member

spydon commented Nov 8, 2022

Do you want to continue on this PR @rivella50 or should we take it over? :)
(Great work so far btw!)

@rivella50
Copy link
Contributor Author

@spydon Feel free to take it over. My current project doesn't let me much free time at the moment.

@spydon spydon marked this pull request as draft February 22, 2023 09:30
@spydon
Copy link
Member

spydon commented Apr 16, 2023

I'll close this for now since it's been open for so long, but we should definitely look at this when creating these components based on the AlignComponent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Row and Column for Flame Components
6 participants