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

Basic flex support #1021

Merged
merged 17 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/core/lib/src/core_widget_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'core_html_widget.dart';
import 'internal/core_ops.dart';
import 'internal/core_parser.dart';
import 'internal/margin_vertical.dart';
import 'internal/ops/style_display_flex.dart';
import 'internal/platform_specific/fallback.dart'
if (dart.library.io) 'internal/platform_specific/io.dart';
import 'internal/text_ops.dart' as text_ops;
Expand Down Expand Up @@ -673,7 +674,6 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory {
),
);
break;

case 'article':
case 'aside':
case 'dl':
Expand Down Expand Up @@ -1134,6 +1134,9 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory {
StyleSizing.maybeRegisterChildOp(this, tree);

switch (value) {
case kCssDisplayFlex:
tree.register(StyleDisplayFlexOps.flexOp(tree));
break;
case kCssDisplayBlock:
StyleSizing.registerBlockOp(this, tree);
break;
Expand Down
1 change: 1 addition & 0 deletions packages/core/lib/src/internal/core_ops.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const kCssDisplay = 'display';
const kCssDisplayBlock = 'block';
const kCssDisplayInline = 'inline';
const kCssDisplayInlineBlock = 'inline-block';
const kCssDisplayFlex = 'flex';
const kCssDisplayNone = 'none';

const kCssLineHeight = 'line-height';
Expand Down
115 changes: 115 additions & 0 deletions packages/core/lib/src/internal/ops/style_display_flex.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'package:flutter/material.dart';

import '../../../flutter_widget_from_html_core.dart';
import '../core_ops.dart';

const kCssFlexDirection = 'flex-direction';
const kCssFlexDirectionRow = 'row';
const kCssJustifyContent = 'justify-content';
const kCssJustifyContentFlexStart = 'flex-start';
const kCssJustifyContentFlexEnd = 'flex-end';
const kCssJustifyContentCenter = 'center';
const kCssJustifyContentSpaceBetween = 'space-between';
const kCssJustifyContentSpaceAround = 'space-around';
const kCssJustifyContentSpaceEvenly = 'space-evenly';
const kCssAlignItems = 'align-items';
const kCssAlignItemsFlexStart = 'flex-start';
const kCssAlignItemsFlexEnd = 'flex-end';
const kCssAlignItemsCenter = 'center';
const kCssAlignItemsBaseline = 'baseline';
const kCssAlignItemsStretch = 'stretch';

// ignore: avoid_classes_with_only_static_members
class StyleDisplayFlexOps {
/// Builds custom widget for div elements with display: flex from [meta]
static BuildOp flexOp(BuildTree tree) {
return BuildOp(
onVisitChild: (tree, subTree) {
subTree.register(_flexItemOp(subTree));
},
onRenderBlock: (tree, placeholder) {
final String id = tree.element.id;
String flexDirection = kCssFlexDirectionRow;
String justifyContent = kCssJustifyContentFlexStart;
String alignItems = kCssAlignItemsFlexStart;

for (final element in tree.element.styles) {
final String? value = element.term;

if (value != null) {
switch (element.property) {
case kCssFlexDirection:
flexDirection = value;
break;
case kCssJustifyContent:
justifyContent = value;
break;
case kCssAlignItems:
alignItems = value;
break;
}
}
}

return Flex(
key: Key(id),
direction: kCssFlexDirectionRow == flexDirection
? Axis.horizontal
: Axis.vertical,
mainAxisAlignment: _toMainAxisAlignment(justifyContent),
crossAxisAlignment: _toCrossAxisAlignment(alignItems),
children: [
placeholder,
],
);
},
);
}

/// Build op for child elements of flex containers
static BuildOp _flexItemOp(BuildTree tree) {
return BuildOp(
defaultStyles: (element) {
return {kCssWidth: kCssWidthAuto, kCssHeight: kCssHeightAuto};
},
);
}

/// Converts CSS [justifyContent] to Flutter Grid MainAxisAlignment
static MainAxisAlignment _toMainAxisAlignment(String justifyContent) {
switch (justifyContent) {
case kCssJustifyContentFlexStart:
return MainAxisAlignment.start;
case kCssJustifyContentFlexEnd:
return MainAxisAlignment.end;
case kCssJustifyContentCenter:
return MainAxisAlignment.center;
case kCssJustifyContentSpaceBetween:
return MainAxisAlignment.spaceBetween;
case kCssJustifyContentSpaceAround:
return MainAxisAlignment.spaceAround;
case kCssJustifyContentSpaceEvenly:
return MainAxisAlignment.spaceEvenly;
default:
return MainAxisAlignment.start;
}
}

/// Converts CSS [alignItems] to Flutter Grid CrossAxisAlignment
static CrossAxisAlignment _toCrossAxisAlignment(String alignItems) {
switch (alignItems) {
case kCssAlignItemsFlexStart:
return CrossAxisAlignment.start;
case kCssAlignItemsFlexEnd:
return CrossAxisAlignment.end;
case kCssAlignItemsCenter:
return CrossAxisAlignment.center;
case kCssAlignItemsBaseline:
return CrossAxisAlignment.baseline;
case kCssAlignItemsStretch:
return CrossAxisAlignment.stretch;
default:
return CrossAxisAlignment.start;
}
}
}
2 changes: 2 additions & 0 deletions packages/core/lib/src/internal/ops/style_sizing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const kCssMaxWidth = 'max-width';
const kCssMinHeight = 'min-height';
const kCssMinWidth = 'min-width';
const kCssWidth = 'width';
const kCssWidthAuto = 'auto';
const kCssHeightAuto = 'auto';

class StyleSizing {
static const k100percent = CssLength(100, CssLengthUnit.percentage);
Expand Down
20 changes: 20 additions & 0 deletions packages/core/test/_.dart
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,22 @@ class Explainer {
return '+w${FontWeight.values.indexOf(fontWeight)}';
}

List<String> _flex(Flex flex) {
final List<String> result = [];

result.add(
'direction=${flex.direction.toString().replaceFirst('Axis.', '')}',
);
result.add(
'mainAxisAlignment=${flex.mainAxisAlignment.toString().replaceFirst('MainAxisAlignment.', '')}',
);
result.add(
'crossAxisAlignment=${flex.crossAxisAlignment.toString().replaceFirst('CrossAxisAlignment.', '')}',
);

return result;
}

String _widget(Widget widget) {
final explained = explainer?.call(this, widget);
if (explained != null) {
Expand Down Expand Up @@ -756,6 +772,10 @@ class Explainer {
attr.add('message=${widget.message}');
}

if (widget is! Column && (widget is Flex)) {
attr.addAll(_flex(widget));
}

// Special cases
// `RichText` is a `MultiChildRenderObjectWidget` but needs different logic
attr.add(
Expand Down
178 changes: 178 additions & 0 deletions packages/core/test/style_display_flex_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'package:flutter_test/flutter_test.dart';

import '_.dart';

void main() {
group('basic usage', () {
const html = '<div style="display: flex"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('horizontal alignment - flex start', () {
const html = '<div style="display: flex; align-items: flex-start;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('horizontal alignment - center', () {
const html = '<div style="display: flex; align-items: flex-center;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('horizontal alignment - flex end', () {
const html = '<div style="display: flex; align-items: flex-end;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=end,children=[widget0]]',
),
);
});
});

group('horizontal alignment - stretch', () {
const html = '<div style="display: flex; align-items: stretch;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=stretch,children=[widget0]]',
),
);
});
});

group('vertical alignment - flex start', () {
const html =
'<div style="display: flex; justify-content: flex-start;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('vertical alignment - center', () {
const html = '<div style="display: flex; justify-content: center;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=center,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('vertical alignment - flex end', () {
const html =
'<div style="display: flex; justify-content: flex-end;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=end,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('vertical alignment - space between', () {
const html =
'<div style="display: flex; justify-content: space-between;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=spaceBetween,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('vertical alignment - space around', () {
const html =
'<div style="display: flex; justify-content: space-around;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=spaceAround,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('vertical alignment - space evenly', () {
const html =
'<div style="display: flex; justify-content: space-evenly;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=spaceEvenly,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});

group('flex direction - row', () {
const html = '<div style="display: flex; flex-direction: row;"></div>';

testWidgets('renders', (WidgetTester tester) async {
final explained = await explain(tester, html);
expect(
explained,
equals(
'[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]',
),
);
});
});
}