Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
ea451690
Commit
ea451690
authored
Sep 23, 2015
by
Hixie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fn3: Port HomogeneousViewport
parent
1836ca61
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
524 additions
and
150 deletions
+524
-150
fn3.dart
packages/flutter/lib/src/fn3.dart
+1
-0
framework.dart
packages/flutter/lib/src/fn3/framework.dart
+175
-150
homogeneous_viewport.dart
packages/flutter/lib/src/fn3/homogeneous_viewport.dart
+176
-0
homogeneous_viewport_test.dart
packages/unit/test/fn3/homogeneous_viewport_test.dart
+172
-0
No files found.
packages/flutter/lib/src/fn3.dart
View file @
ea451690
...
...
@@ -7,3 +7,4 @@ library fn3;
export
'fn3/basic.dart'
;
export
'fn3/framework.dart'
;
export
'fn3/binding.dart'
;
export
'fn3/homogeneous_viewport.dart'
;
packages/flutter/lib/src/fn3/framework.dart
View file @
ea451690
...
...
@@ -504,7 +504,9 @@ abstract class Element<T extends Widget> implements BuildContext {
}
/// Called when an Element is given a new parent shortly after having been
/// created.
/// created. Use this to initialize state that depends on having a parent. For
/// state that is independent of the position in the tree, it's better to just
/// initialize the Element in the constructor.
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
initial
);
assert
(
widget
!=
null
);
...
...
@@ -599,13 +601,13 @@ abstract class Element<T extends Widget> implements BuildContext {
}
}
typedef
Widget
WidgetBuilder
(
BuildContext
context
);
typedef
void
BuildScheduler
(
BuildableElement
element
);
class
ErrorWidget
extends
LeafRenderObjectWidget
{
RenderBox
createRenderObject
()
=>
new
RenderErrorBox
();
}
typedef
Widget
WidgetBuilder
(
BuildContext
context
);
typedef
void
BuildScheduler
(
BuildableElement
element
);
/// Base class for the instantiation of StatelessComponent and StatefulComponent
/// widgets.
abstract
class
BuildableElement
<
T
extends
Widget
>
extends
Element
<
T
>
{
...
...
@@ -629,7 +631,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// stateless components) or the ComponentState object (for stateful
/// components) and then updates the widget tree.
///
/// Called automatically during
didM
ount() to generate the first build, by the
/// Called automatically during
m
ount() to generate the first build, by the
/// binding when scheduleBuild() has been called to mark this element dirty,
/// and by update() when the Widget has changed.
void
rebuild
()
{
...
...
@@ -658,6 +660,24 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
static
BuildScheduler
scheduleBuildFor
;
static
int
_debugStateLockLevel
=
0
;
static
bool
get
_debugStateLocked
=>
_debugStateLockLevel
>
0
;
/// Calls the callback argument synchronously, but in a context where calls to
/// ComponentState.setState() will fail. Use this when it is possible that you
/// will trigger code in components but want to make sure that there is no
/// possibility that any components will be marked dirty, for example because
/// you are in the middle of layout and you are not going to be flushing the
/// build queue (since that could mutate the layout tree).
static
void
lockState
(
void
callback
())
{
_debugStateLockLevel
+=
1
;
try
{
callback
();
}
finally
{
_debugStateLockLevel
-=
1
;
}
}
/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
///
...
...
@@ -666,6 +686,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// components dirty during event handlers before the frame begins, not during
/// the build itself.
void
markNeedsBuild
()
{
assert
(!
_debugStateLocked
);
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(
_dirty
)
return
;
...
...
@@ -804,6 +825,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
/// The underlying [RenderObject] for this element
RenderObject
get
renderObject
=>
_renderObject
;
final
RenderObject
_renderObject
;
RenderObjectElement
_ancestorRenderObjectElement
;
RenderObjectElement
_findAncestorRenderObjectElement
()
{
...
...
@@ -840,6 +862,153 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
widget
.
updateRenderObject
(
renderObject
,
oldWidget
);
}
/// Utility function for subclasses that have one or more lists of children.
/// Attempts to update the given old children list using the given new
/// widgets, removing obsolete elements and introducing new ones as necessary,
/// and then returns the new child list.
List
<
Element
>
updateChildren
(
List
<
Element
>
oldChildren
,
List
<
Widget
>
newWidgets
)
{
assert
(
oldChildren
!=
null
);
assert
(
newWidgets
!=
null
);
// This attempts to diff the new child list (this.children) with
// the old child list (old.children), and update our renderObject
// accordingly.
// The cases it tries to optimise for are:
// - the old list is empty
// - the lists are identical
// - there is an insertion or removal of one or more widgets in
// only one place in the list
// If a widget with a key is in both lists, it will be synced.
// Widgets without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the top until you no longer have
// matching nodes. We don't sync these yet, but we now know to
// skip them below. We do this because at each sync we need to
// pass the pointer to the new next widget as the slot, which
// we can't do until we've synced the next child.
// 2. Walk the lists from the bottom, syncing nodes, until you no
// longer have matching nodes.
// At this point we narrowed the old and new lists to the point
// where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list backwards:
// * Sync unkeyed items with null
// * Sync keyed items with the source if it exists, else with null.
// 5. Walk the top list again but backwards, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still
// mounted.
final
ContainerRenderObjectMixin
renderObject
=
this
.
renderObject
;
// TODO(ianh): Remove this once the analyzer is cleverer
assert
(
renderObject
is
ContainerRenderObjectMixin
);
int
childrenTop
=
0
;
int
newChildrenBottom
=
newWidgets
.
length
-
1
;
int
oldChildrenBottom
=
oldChildren
.
length
-
1
;
// top of the lists
while
((
childrenTop
<=
oldChildrenBottom
)
&&
(
childrenTop
<=
newChildrenBottom
))
{
Element
oldChild
=
oldChildren
[
childrenTop
];
Widget
newWidget
=
newWidgets
[
childrenTop
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(!
_canUpdate
(
oldChild
.
widget
,
newWidget
))
break
;
childrenTop
+=
1
;
}
List
<
Element
>
newChildren
=
oldChildren
.
length
==
newWidgets
.
length
?
oldChildren
:
new
List
<
Element
>(
newWidgets
.
length
);
Element
nextSibling
;
// bottom of the lists
while
((
childrenTop
<=
oldChildrenBottom
)
&&
(
childrenTop
<=
newChildrenBottom
))
{
Element
oldChild
=
oldChildren
[
oldChildrenBottom
];
Widget
newWidget
=
newWidgets
[
newChildrenBottom
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(!
_canUpdate
(
oldChild
.
widget
,
newWidget
))
break
;
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
nextSibling
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
newChildren
[
newChildrenBottom
]
=
newChild
;
nextSibling
=
newChild
;
oldChildrenBottom
-=
1
;
newChildrenBottom
-=
1
;
}
// middle of the lists - old list
bool
haveOldNodes
=
childrenTop
<=
oldChildrenBottom
;
Map
<
Key
,
Element
>
oldKeyedChildren
;
if
(
haveOldNodes
)
{
oldKeyedChildren
=
new
Map
<
Key
,
Element
>();
while
(
childrenTop
<=
oldChildrenBottom
)
{
Element
oldChild
=
oldChildren
[
oldChildrenBottom
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(
oldChild
.
widget
.
key
!=
null
)
oldKeyedChildren
[
oldChild
.
widget
.
key
]
=
oldChild
;
else
_detachChild
(
oldChild
);
oldChildrenBottom
-=
1
;
}
}
// middle of the lists - new list
while
(
childrenTop
<=
newChildrenBottom
)
{
Element
oldChild
;
Widget
newWidget
=
newWidgets
[
newChildrenBottom
];
if
(
haveOldNodes
)
{
Key
key
=
newWidget
.
key
;
if
(
key
!=
null
)
{
oldChild
=
oldKeyedChildren
[
newWidget
.
key
];
if
(
oldChild
!=
null
)
{
if
(
_canUpdate
(
oldChild
.
widget
,
newWidget
))
{
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
oldKeyedChildren
.
remove
(
key
);
}
else
{
// Not a match, let's pretend we didn't see it for now.
oldChild
=
null
;
}
}
}
}
assert
(
oldChild
==
null
||
_canUpdate
(
oldChild
.
widget
,
newWidget
));
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
nextSibling
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_debugLifecycleState
!=
_ElementLifecycle
.
mounted
);
newChildren
[
newChildrenBottom
]
=
newChild
;
nextSibling
=
newChild
;
newChildrenBottom
-=
1
;
}
assert
(
oldChildrenBottom
==
newChildrenBottom
);
assert
(
childrenTop
==
newChildrenBottom
+
1
);
// now sync the top of the list
while
(
childrenTop
>
0
)
{
childrenTop
-=
1
;
Element
oldChild
=
oldChildren
[
childrenTop
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
Widget
newWidget
=
newWidgets
[
childrenTop
];
assert
(
_canUpdate
(
oldChild
.
widget
,
newWidget
));
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
nextSibling
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_debugLifecycleState
!=
_ElementLifecycle
.
mounted
);
newChildren
[
childrenTop
]
=
newChild
;
nextSibling
=
newChild
;
}
// clean up any of the remaining middle nodes from the old list
if
(
haveOldNodes
&&
!
oldKeyedChildren
.
isEmpty
)
{
for
(
Element
oldChild
in
oldKeyedChildren
.
values
)
_detachChild
(
oldChild
);
}
assert
(
renderObject
==
this
.
renderObject
);
// TODO(ianh): Remove this once the analyzer is cleverer
return
newChildren
;
}
void
unmount
()
{
super
.
unmount
();
widget
.
didUnmountRenderObject
(
renderObject
);
...
...
@@ -995,152 +1164,8 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
void
update
(
T
newWidget
)
{
super
.
update
(
newWidget
);
assert
(
widget
==
newWidget
);
_children
=
_updateChildren
(
_children
,
widget
.
children
);
}
List
<
Element
>
_updateChildren
(
List
<
Element
>
oldChildren
,
List
<
Widget
>
newWidgets
)
{
assert
(
oldChildren
!=
null
);
assert
(
newWidgets
!=
null
);
// This attempts to diff the new child list (this.children) with
// the old child list (old.children), and update our renderObject
// accordingly.
// The cases it tries to optimise for are:
// - the old list is empty
// - the lists are identical
// - there is an insertion or removal of one or more widgets in
// only one place in the list
// If a widget with a key is in both lists, it will be synced.
// Widgets without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the top until you no longer have
// matching nodes. We don't sync these yet, but we now know to
// skip them below. We do this because at each sync we need to
// pass the pointer to the new next widget as the slot, which
// we can't do until we've synced the next child.
// 2. Walk the lists from the bottom, syncing nodes, until you no
// longer have matching nodes.
// At this point we narrowed the old and new lists to the point
// where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list backwards:
// * Sync unkeyed items with null
// * Sync keyed items with the source if it exists, else with null.
// 5. Walk the top list again but backwards, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still
// mounted.
final
ContainerRenderObjectMixin
renderObject
=
this
.
renderObject
;
// TODO(ianh): Remove this once the analyzer is cleverer
assert
(
renderObject
is
ContainerRenderObjectMixin
);
int
childrenTop
=
0
;
int
newChildrenBottom
=
newWidgets
.
length
-
1
;
int
oldChildrenBottom
=
oldChildren
.
length
-
1
;
// top of the lists
while
((
childrenTop
<=
oldChildrenBottom
)
&&
(
childrenTop
<=
newChildrenBottom
))
{
Element
oldChild
=
oldChildren
[
childrenTop
];
Widget
newWidget
=
newWidgets
[
childrenTop
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(!
_canUpdate
(
oldChild
.
widget
,
newWidget
))
break
;
childrenTop
+=
1
;
}
List
<
Element
>
newChildren
=
oldChildren
.
length
==
newWidgets
.
length
?
oldChildren
:
new
List
<
Element
>(
newWidgets
.
length
);
Element
nextSibling
;
// bottom of the lists
while
((
childrenTop
<=
oldChildrenBottom
)
&&
(
childrenTop
<=
newChildrenBottom
))
{
Element
oldChild
=
oldChildren
[
oldChildrenBottom
];
Widget
newWidget
=
newWidgets
[
newChildrenBottom
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(!
_canUpdate
(
oldChild
.
widget
,
newWidget
))
break
;
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
nextSibling
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
newChildren
[
newChildrenBottom
]
=
newChild
;
nextSibling
=
newChild
;
oldChildrenBottom
-=
1
;
newChildrenBottom
-=
1
;
}
// middle of the lists - old list
bool
haveOldNodes
=
childrenTop
<=
oldChildrenBottom
;
Map
<
Key
,
Element
>
oldKeyedChildren
;
if
(
haveOldNodes
)
{
oldKeyedChildren
=
new
Map
<
Key
,
Element
>();
while
(
childrenTop
<=
oldChildrenBottom
)
{
Element
oldChild
=
oldChildren
[
oldChildrenBottom
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
if
(
oldChild
.
widget
.
key
!=
null
)
oldKeyedChildren
[
oldChild
.
widget
.
key
]
=
oldChild
;
else
_detachChild
(
oldChild
);
oldChildrenBottom
-=
1
;
}
}
// middle of the lists - new list
while
(
childrenTop
<=
newChildrenBottom
)
{
Element
oldChild
;
Widget
newWidget
=
newWidgets
[
newChildrenBottom
];
if
(
haveOldNodes
)
{
Key
key
=
newWidget
.
key
;
if
(
key
!=
null
)
{
oldChild
=
oldKeyedChildren
[
newWidget
.
key
];
if
(
oldChild
!=
null
)
{
if
(
_canUpdate
(
oldChild
.
widget
,
newWidget
))
{
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
oldKeyedChildren
.
remove
(
key
);
}
else
{
// Not a match, let's pretend we didn't see it for now.
oldChild
=
null
;
}
_children
=
updateChildren
(
_children
,
widget
.
children
);
}
}
}
assert
(
oldChild
==
null
||
_canUpdate
(
oldChild
.
widget
,
newWidget
));
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
nextSibling
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_debugLifecycleState
!=
_ElementLifecycle
.
mounted
);
newChildren
[
newChildrenBottom
]
=
newChild
;
nextSibling
=
newChild
;
newChildrenBottom
-=
1
;
}
assert
(
oldChildrenBottom
==
newChildrenBottom
);
assert
(
childrenTop
==
newChildrenBottom
+
1
);
// now sync the top of the list
while
(
childrenTop
>
0
)
{
childrenTop
-=
1
;
Element
oldChild
=
oldChildren
[
childrenTop
];
assert
(
oldChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
Widget
newWidget
=
newWidgets
[
childrenTop
];
assert
(
_canUpdate
(
oldChild
.
widget
,
newWidget
));
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
nextSibling
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_debugLifecycleState
!=
_ElementLifecycle
.
mounted
);
newChildren
[
childrenTop
]
=
newChild
;
nextSibling
=
newChild
;
}
// clean up any of the remaining middle nodes from the old list
if
(
haveOldNodes
&&
!
oldKeyedChildren
.
isEmpty
)
{
for
(
Element
oldChild
in
oldKeyedChildren
.
values
)
_detachChild
(
oldChild
);
}
assert
(
renderObject
==
this
.
renderObject
);
// TODO(ianh): Remove this once the analyzer is cleverer
return
newChildren
;
}
}
typedef
void
WidgetsExceptionHandler
(
String
context
,
dynamic
exception
,
StackTrace
stack
);
...
...
packages/flutter/lib/src/fn3/homogeneous_viewport.dart
0 → 100644
View file @
ea451690
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'package:sky/rendering.dart'
;
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/basic.dart'
;
typedef
List
<
Widget
>
ListBuilder
(
int
startIndex
,
int
count
,
BuildContext
context
);
class
HomogeneousViewport
extends
RenderObjectWidget
{
HomogeneousViewport
({
Key
key
,
this
.
builder
,
this
.
itemsWrap
:
false
,
this
.
itemExtent
,
// required
this
.
itemCount
,
// optional, but you cannot shrink-wrap this class or otherwise use its intrinsic dimensions if you don't specify it
this
.
direction
:
ScrollDirection
.
vertical
,
this
.
startOffset
:
0.0
})
:
super
(
key:
key
)
{
assert
(
itemExtent
!=
null
);
}
final
ListBuilder
builder
;
final
bool
itemsWrap
;
final
double
itemExtent
;
final
int
itemCount
;
final
ScrollDirection
direction
;
final
double
startOffset
;
RenderObjectElement
createElement
()
=>
new
HomogeneousViewportElement
(
this
);
// we don't pass constructor arguments to the RenderBlockViewport() because until
// we know our children, the constructor arguments we could give have no effect
RenderObject
createRenderObject
()
=>
new
RenderBlockViewport
();
bool
isLayoutDifferentThan
(
HomogeneousViewport
oldWidget
)
{
return
itemsWrap
!=
oldWidget
.
itemsWrap
||
itemsWrap
!=
oldWidget
.
itemsWrap
||
itemExtent
!=
oldWidget
.
itemExtent
||
itemCount
!=
oldWidget
.
itemCount
||
direction
!=
oldWidget
.
direction
||
startOffset
!=
oldWidget
.
startOffset
;
}
// all the actual work is done in the element
}
class
HomogeneousViewportElement
extends
RenderObjectElement
<
HomogeneousViewport
>
{
HomogeneousViewportElement
(
HomogeneousViewport
widget
)
:
super
(
widget
);
List
<
Element
>
_children
=
const
<
Element
>[];
bool
_layoutDirty
=
true
;
int
_layoutFirstIndex
;
int
_layoutItemCount
;
RenderBlockViewport
get
renderObject
=>
super
.
renderObject
;
void
visitChildren
(
ElementVisitor
visitor
)
{
if
(
_children
==
null
)
return
;
for
(
Element
child
in
_children
)
visitor
(
child
);
}
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
renderObject
.
callback
=
layout
;
renderObject
.
totalExtentCallback
=
getTotalExtent
;
renderObject
.
minCrossAxisExtentCallback
=
getMinCrossAxisExtent
;
renderObject
.
maxCrossAxisExtentCallback
=
getMaxCrossAxisExtent
;
}
void
unmount
()
{
renderObject
.
callback
=
null
;
renderObject
.
totalExtentCallback
=
null
;
renderObject
.
minCrossAxisExtentCallback
=
null
;
renderObject
.
maxCrossAxisExtentCallback
=
null
;
super
.
unmount
();
}
void
update
(
HomogeneousViewport
newWidget
)
{
bool
needLayout
=
newWidget
.
isLayoutDifferentThan
(
widget
);
super
.
update
(
newWidget
);
if
(
needLayout
)
renderObject
.
markNeedsLayout
();
else
_updateChildren
();
}
void
layout
(
BoxConstraints
constraints
)
{
// we lock the framework state (meaning that no elements can call markNeedsBuild()) because we are
// in the middle of layout and if we allowed people to set state, they'd expect to have that state
// reflected immediately, which, if we were to try to honour it, would potentially result in
// assertions since you can't normally mutate the render object tree during layout. (If there was
// a way to limit this to only descendants of this, it'd be ok, since we are exempt from that
// assert since we are actively doing our own layout still.)
BuildableElement
.
lockState
(()
{
double
mainAxisExtent
=
widget
.
direction
==
ScrollDirection
.
vertical
?
constraints
.
maxHeight
:
constraints
.
maxWidth
;
double
offset
;
if
(
widget
.
startOffset
<=
0.0
&&
!
widget
.
itemsWrap
)
{
_layoutFirstIndex
=
0
;
offset
=
-
widget
.
startOffset
;
}
else
{
_layoutFirstIndex
=
(
widget
.
startOffset
/
widget
.
itemExtent
).
floor
();
offset
=
-(
widget
.
startOffset
%
widget
.
itemExtent
);
}
if
(
mainAxisExtent
<
double
.
INFINITY
)
{
_layoutItemCount
=
((
mainAxisExtent
-
offset
)
/
widget
.
itemExtent
).
ceil
();
if
(
widget
.
itemCount
!=
null
&&
!
widget
.
itemsWrap
)
_layoutItemCount
=
math
.
min
(
_layoutItemCount
,
widget
.
itemCount
-
_layoutFirstIndex
);
}
else
{
assert
(()
{
'This HomogeneousViewport has no specified number of items (meaning it has infinite items), '
+
'and has been placed in an unconstrained environment where all items can be rendered. '
+
'It is most likely that you have placed your HomogeneousViewport (which is an internal '
+
'component of several scrollable widgets) inside either another scrolling box, a flexible '
+
'box (Row, Column), or a Stack, without giving it a specific size.'
;
return
widget
.
itemCount
!=
null
;
});
_layoutItemCount
=
widget
.
itemCount
-
_layoutFirstIndex
;
}
_layoutItemCount
=
math
.
max
(
0
,
_layoutItemCount
);
_updateChildren
();
// Update the renderObject configuration
renderObject
.
direction
=
widget
.
direction
==
ScrollDirection
.
vertical
?
BlockDirection
.
vertical
:
BlockDirection
.
horizontal
;
renderObject
.
itemExtent
=
widget
.
itemExtent
;
renderObject
.
minExtent
=
getTotalExtent
(
null
);
renderObject
.
startOffset
=
offset
;
});
}
void
_updateChildren
()
{
assert
(
_layoutFirstIndex
!=
null
);
assert
(
_layoutItemCount
!=
null
);
List
<
Widget
>
newWidgets
;
if
(
_layoutItemCount
>
0
)
newWidgets
=
widget
.
builder
(
_layoutFirstIndex
,
_layoutItemCount
,
this
);
else
newWidgets
=
<
Widget
>[];
_children
=
updateChildren
(
_children
,
newWidgets
);
}
double
getTotalExtent
(
BoxConstraints
constraints
)
{
// constraints is null when called by layout() above
return
widget
.
itemCount
!=
null
?
widget
.
itemCount
*
widget
.
itemExtent
:
double
.
INFINITY
;
}
double
getMinCrossAxisExtent
(
BoxConstraints
constraints
)
{
return
0.0
;
}
double
getMaxCrossAxisExtent
(
BoxConstraints
constraints
)
{
if
(
widget
.
direction
==
ScrollDirection
.
vertical
)
return
constraints
.
maxWidth
;
return
constraints
.
maxHeight
;
}
void
insertChildRenderObject
(
RenderObject
child
,
Element
slot
)
{
RenderObject
nextSibling
=
slot
?.
renderObject
;
renderObject
.
add
(
child
,
before:
nextSibling
);
}
void
moveChildRenderObject
(
RenderObject
child
,
dynamic
slot
)
{
RenderObject
nextSibling
=
slot
?.
renderObject
;
renderObject
.
move
(
child
,
before:
nextSibling
);
}
void
removeChildRenderObject
(
RenderObject
child
)
{
assert
(
child
.
parent
==
renderObject
);
renderObject
.
remove
(
child
);
}
}
packages/unit/test/fn3/homogeneous_viewport_test.dart
0 → 100644
View file @
ea451690
import
'package:sky/src/fn3.dart'
;
import
'package:test/test.dart'
;
import
'widget_tester.dart'
;
class
TestComponent
extends
StatefulComponent
{
TestComponent
(
this
.
viewport
);
final
HomogeneousViewport
viewport
;
TestComponentState
createState
()
=>
new
TestComponentState
(
this
);
}
class
TestComponentState
extends
ComponentState
<
TestComponent
>
{
TestComponentState
(
TestComponent
config
):
super
(
config
);
bool
_flag
=
true
;
void
go
(
bool
flag
)
{
setState
(()
{
_flag
=
flag
;
});
}
Widget
build
(
BuildContext
context
)
{
return
_flag
?
config
.
viewport
:
new
Text
(
'Not Today'
);
}
}
void
main
(
)
{
test
(
'HomogeneousViewport mount/dismount smoke test'
,
()
{
WidgetTester
tester
=
new
WidgetTester
();
List
<
int
>
callbackTracker
=
<
int
>[];
// the root view is 800x600 in the test environment
// so if our widget is 100 pixels tall, it should fit exactly 6 times.
Widget
builder
()
{
return
new
TestComponent
(
new
HomogeneousViewport
(
builder:
(
int
start
,
int
count
,
BuildContext
context
)
{
List
<
Widget
>
result
=
<
Widget
>[];
for
(
int
index
=
start
;
index
<
start
+
count
;
index
+=
1
)
{
callbackTracker
.
add
(
index
);
result
.
add
(
new
Container
(
key:
new
ValueKey
<
int
>(
index
),
height:
100.0
,
child:
new
Text
(
"
$index
"
)
));
}
return
result
;
},
startOffset:
0.0
,
itemExtent:
100.0
));
}
tester
.
pumpFrame
(
builder
());
TestComponentState
testComponent
=
tester
.
findElement
((
element
)
=>
element
.
widget
is
TestComponent
).
state
;
expect
(
callbackTracker
,
equals
([
0
,
1
,
2
,
3
,
4
,
5
]));
callbackTracker
.
clear
();
testComponent
.
go
(
false
);
tester
.
pumpFrameWithoutChange
();
expect
(
callbackTracker
,
equals
([]));
callbackTracker
.
clear
();
testComponent
.
go
(
true
);
tester
.
pumpFrameWithoutChange
();
expect
(
callbackTracker
,
equals
([
0
,
1
,
2
,
3
,
4
,
5
]));
});
test
(
'HomogeneousViewport vertical'
,
()
{
WidgetTester
tester
=
new
WidgetTester
();
List
<
int
>
callbackTracker
=
<
int
>[];
// the root view is 800x600 in the test environment
// so if our widget is 200 pixels tall, it should fit exactly 3 times.
// but if we are offset by 300 pixels, there will be 4, numbered 1-4.
double
offset
=
300.0
;
ListBuilder
itemBuilder
=
(
int
start
,
int
count
,
BuildContext
context
)
{
List
<
Widget
>
result
=
<
Widget
>[];
for
(
int
index
=
start
;
index
<
start
+
count
;
index
+=
1
)
{
callbackTracker
.
add
(
index
);
result
.
add
(
new
Container
(
key:
new
ValueKey
<
int
>(
index
),
width:
500.0
,
// this should be ignored
height:
400.0
,
// should be overridden by itemExtent
child:
new
Text
(
"
$index
"
)
));
}
return
result
;
};
TestComponent
testComponent
;
Widget
builder
()
{
testComponent
=
new
TestComponent
(
new
HomogeneousViewport
(
builder:
itemBuilder
,
startOffset:
offset
,
itemExtent:
200.0
));
return
testComponent
;
}
tester
.
pumpFrame
(
builder
());
expect
(
callbackTracker
,
equals
([
1
,
2
,
3
,
4
]));
callbackTracker
.
clear
();
offset
=
400.0
;
// now only 3 should fit, numbered 2-4.
tester
.
pumpFrame
(
builder
());
expect
(
callbackTracker
,
equals
([
2
,
3
,
4
]));
callbackTracker
.
clear
();
});
test
(
'HomogeneousViewport horizontal'
,
()
{
WidgetTester
tester
=
new
WidgetTester
();
List
<
int
>
callbackTracker
=
<
int
>[];
// the root view is 800x600 in the test environment
// so if our widget is 200 pixels wide, it should fit exactly 4 times.
// but if we are offset by 300 pixels, there will be 5, numbered 1-5.
double
offset
=
300.0
;
ListBuilder
itemBuilder
=
(
int
start
,
int
count
,
BuildContext
context
)
{
List
<
Widget
>
result
=
<
Widget
>[];
for
(
int
index
=
start
;
index
<
start
+
count
;
index
+=
1
)
{
callbackTracker
.
add
(
index
);
result
.
add
(
new
Container
(
key:
new
ValueKey
<
int
>(
index
),
width:
400.0
,
// this should be overridden by itemExtent
height:
500.0
,
// this should be ignored
child:
new
Text
(
"
$index
"
)
));
}
return
result
;
};
TestComponent
testComponent
;
Widget
builder
()
{
testComponent
=
new
TestComponent
(
new
HomogeneousViewport
(
builder:
itemBuilder
,
startOffset:
offset
,
itemExtent:
200.0
,
direction:
ScrollDirection
.
horizontal
));
return
testComponent
;
}
tester
.
pumpFrame
(
builder
());
expect
(
callbackTracker
,
equals
([
1
,
2
,
3
,
4
,
5
]));
callbackTracker
.
clear
();
offset
=
400.0
;
// now only 4 should fit, numbered 2-5.
tester
.
pumpFrame
(
builder
());
expect
(
callbackTracker
,
equals
([
2
,
3
,
4
,
5
]));
callbackTracker
.
clear
();
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment