Commit 90363ee9 authored by Viktor Lidholt's avatar Viktor Lidholt

Merge branch 'master' of github.com:domokit/sky_engine into HEAD

parents 1b4923a2 df88d38b
...@@ -120,7 +120,7 @@ dart_pkg("sky") { ...@@ -120,7 +120,7 @@ dart_pkg("sky") {
sdk_ext_mappings = [ sdk_ext_mappings = [
"dart:sky,dart_sky.dart", "dart:sky,dart_sky.dart",
"dart:sky.internals,sky_internals.dart", "dart:sky.internals,sky_internals.dart",
"dart:sky_builtin_natvies,builtin_natives.dart", "dart:sky_builtin_natives,builtin_natives.dart",
] ]
} }
......
...@@ -39,8 +39,8 @@ void main() { ...@@ -39,8 +39,8 @@ void main() {
} }
``` ```
Execution starts in `main`, which runs a new instance of the `HelloWorldApp`. Execution starts in `main`, which in this example runs a new instance of the `HelloWorldApp`.
The `HelloWorldApp` builds a `Text` widget containing the famous `Hello, world!` The `HelloWorldApp` builds a `Text` widget containing the traditional `Hello, world!`
string and centers it on the screen using a `Center` widget. To learn more about string and centers it on the screen using a `Center` widget. To learn more about
the widget system, please see the [widgets tutorial](lib/widgets/README.md). the widget system, please see the [widgets tutorial](lib/widgets/README.md).
......
...@@ -80,7 +80,7 @@ List<SkyDemo> demos = [ ...@@ -80,7 +80,7 @@ List<SkyDemo> demos = [
name: 'Asteroids', name: 'Asteroids',
href: '../../game/main.dart', href: '../../game/main.dart',
bundle: 'game.skyx', bundle: 'game.skyx',
description: '2D game using sprite sheets to achieve high performance', description: '2D game using sprite sheets',
textTheme: typography.white, textTheme: typography.white,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundImage: new BackgroundImage( backgroundImage: new BackgroundImage(
...@@ -93,20 +93,20 @@ List<SkyDemo> demos = [ ...@@ -93,20 +93,20 @@ List<SkyDemo> demos = [
name: 'Fitness', name: 'Fitness',
href: '../../fitness/lib/main.dart', href: '../../fitness/lib/main.dart',
bundle: 'fitness.skyx', bundle: 'fitness.skyx',
description: 'Collin should write a nice description', description: 'Track progress towards healthy goals',
textTheme: typography.white, textTheme: typography.white,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: const Color(0xFF0081C6) backgroundColor: colors.Indigo[500]
) )
), ),
new SkyDemo( new SkyDemo(
name: 'Cards', name: 'Swipe Away',
href: '../../widgets/card_collection.dart', href: '../../widgets/card_collection.dart',
bundle: 'cards.skyx', bundle: 'cards.skyx',
description: 'Demo of interactive Cards', description: 'Infinite list of swipeable cards',
textTheme: typography.white, textTheme: typography.white,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: const Color(0xFF0081C6) backgroundColor: colors.RedAccent[200]
) )
), ),
new SkyDemo( new SkyDemo(
...@@ -127,7 +127,10 @@ List<SkyDemo> demos = [ ...@@ -127,7 +127,10 @@ List<SkyDemo> demos = [
href: '../../mine_digger/lib/main.dart', href: '../../mine_digger/lib/main.dart',
bundle: 'mine_digger.skyx', bundle: 'mine_digger.skyx',
description: 'Clone of the classic Minesweeper game', description: 'Clone of the classic Minesweeper game',
textTheme: typography.white textTheme: typography.white,
decoration: new BoxDecoration(
backgroundColor: colors.black
)
), ),
// TODO(jackson): This doesn't seem to be working // TODO(jackson): This doesn't seem to be working
...@@ -143,7 +146,7 @@ class DemoList extends Component { ...@@ -143,7 +146,7 @@ class DemoList extends Component {
decoration: demo.decoration, decoration: demo.decoration,
child: new InkWell( child: new InkWell(
child: new Container( child: new Container(
margin: const EdgeDims.all(24.0), margin: const EdgeDims.only(top: 24.0, left: 24.0),
child: new Flex([ child: new Flex([
new Text(demo.name, style: demo.textTheme.title), new Text(demo.name, style: demo.textTheme.title),
new Flexible( new Flexible(
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/card.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/dismissable.dart';
import 'package:sky/widgets/drawer.dart'; import 'package:sky/widgets/drawer.dart';
import 'package:sky/widgets/drawer_divider.dart'; import 'package:sky/widgets/drawer_divider.dart';
import 'package:sky/widgets/drawer_header.dart'; import 'package:sky/widgets/drawer_header.dart';
...@@ -11,9 +14,11 @@ import 'package:sky/widgets/drawer_item.dart'; ...@@ -11,9 +14,11 @@ import 'package:sky/widgets/drawer_item.dart';
import 'package:sky/widgets/floating_action_button.dart'; import 'package:sky/widgets/floating_action_button.dart';
import 'package:sky/widgets/icon_button.dart'; import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/icon.dart'; import 'package:sky/widgets/icon.dart';
import 'package:sky/widgets/ink_well.dart';
import 'package:sky/widgets/material.dart'; import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/navigator.dart'; import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scaffold.dart'; import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/scrollable_list.dart';
import 'package:sky/widgets/snack_bar.dart'; import 'package:sky/widgets/snack_bar.dart';
import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart'; import 'package:sky/widgets/tool_bar.dart';
...@@ -22,12 +27,79 @@ import 'package:sky/widgets/widget.dart'; ...@@ -22,12 +27,79 @@ import 'package:sky/widgets/widget.dart';
import 'fitness_types.dart'; import 'fitness_types.dart';
import 'measurement.dart'; import 'measurement.dart';
class MeasurementList extends Component {
MeasurementList({ String key, this.measurements, this.onDismissed }) : super(key: key);
final List<Measurement> measurements;
final MeasurementHandler onDismissed;
Widget build() {
return new Material(
type: MaterialType.canvas,
child: new ScrollableList<Measurement>(
items: measurements,
itemHeight: MeasurementRow.kHeight,
itemBuilder: (measurement) => new MeasurementRow(
measurement: measurement,
onDismissed: onDismissed
)
)
);
}
}
class MeasurementRow extends Component {
MeasurementRow({ Measurement measurement, this.onDismissed }) : this.measurement = measurement, super(key: measurement.when.toString());
final Measurement measurement;
final MeasurementHandler onDismissed;
static const double kHeight = 79.0;
Widget build() {
List<Widget> children = [
new Flexible(
child: new Text(
measurement.displayWeight,
style: const TextStyle(textAlign: TextAlign.right)
)
),
new Flexible(
child: new Text(
measurement.displayDate,
style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Dismissable(
key: measurement.when.toString(),
onDismissed: () => onDismissed(measurement),
child: new Card(
child: new Container(
height: kHeight,
padding: const EdgeDims.all(8.0),
child: new Flex(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
)
)
)
);
}
}
class HomeFragment extends StatefulComponent { class HomeFragment extends StatefulComponent {
HomeFragment(this.navigator, this.userData); HomeFragment({ this.navigator, this.userData, this.onMeasurementCreated, this.onMeasurementDeleted });
Navigator navigator; Navigator navigator;
List<Measurement> userData; List<Measurement> userData;
MeasurementHandler onMeasurementCreated;
MeasurementHandler onMeasurementDeleted;
FitnessMode _fitnessMode = FitnessMode.measure; FitnessMode _fitnessMode = FitnessMode.measure;
...@@ -40,6 +112,8 @@ class HomeFragment extends StatefulComponent { ...@@ -40,6 +112,8 @@ class HomeFragment extends StatefulComponent {
void syncFields(HomeFragment source) { void syncFields(HomeFragment source) {
navigator = source.navigator; navigator = source.navigator;
userData = source.userData; userData = source.userData;
onMeasurementCreated = source.onMeasurementCreated;
onMeasurementDeleted = source.onMeasurementDeleted;
} }
bool _isShowingSnackBar = false; bool _isShowingSnackBar = false;
...@@ -121,10 +195,26 @@ class HomeFragment extends StatefulComponent { ...@@ -121,10 +195,26 @@ class HomeFragment extends StatefulComponent {
); );
} }
// TODO(jackson): Pull from file
Measurement _undoMeasurement;
void _handleMeasurementDismissed(Measurement measurement) {
onMeasurementDeleted(measurement);
setState(() {
_undoMeasurement = measurement;
_isShowingSnackBar = true;
});
}
Widget buildBody() { Widget buildBody() {
TextStyle style = Theme.of(this).text.title; TextStyle style = Theme.of(this).text.title;
switch (_fitnessMode) { switch (_fitnessMode) {
case FitnessMode.measure: case FitnessMode.measure:
if (userData.length > 0)
return new MeasurementList(
measurements: userData,
onDismissed: _handleMeasurementDismissed
);
return new Material( return new Material(
type: MaterialType.canvas, type: MaterialType.canvas,
child: new Flex( child: new Flex(
...@@ -143,7 +233,9 @@ class HomeFragment extends StatefulComponent { ...@@ -143,7 +233,9 @@ class HomeFragment extends StatefulComponent {
} }
void _handleUndo() { void _handleUndo() {
onMeasurementCreated(_undoMeasurement);
setState(() { setState(() {
_undoMeasurement = null;
_isShowingSnackBar = false; _isShowingSnackBar = false;
}); });
} }
...@@ -152,17 +244,11 @@ class HomeFragment extends StatefulComponent { ...@@ -152,17 +244,11 @@ class HomeFragment extends StatefulComponent {
if (!_isShowingSnackBar) if (!_isShowingSnackBar)
return null; return null;
return new SnackBar( return new SnackBar(
content: new Text("Measurement added!"), content: new Text("Measurement deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)] actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)]
); );
} }
void _handleMeasurementAdded() {
setState(() {
_isShowingSnackBar = true;
});
}
void _handleRunStarted() { void _handleRunStarted() {
setState(() { setState(() {
_isRunning = true; _isRunning = true;
...@@ -180,7 +266,7 @@ class HomeFragment extends StatefulComponent { ...@@ -180,7 +266,7 @@ class HomeFragment extends StatefulComponent {
case FitnessMode.measure: case FitnessMode.measure:
return new FloatingActionButton( return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24), child: new Icon(type: 'content/add', size: 24),
onPressed: _handleMeasurementAdded onPressed: () => navigator.pushNamed("/measurements/new")
); );
case FitnessMode.run: case FitnessMode.run:
return new FloatingActionButton( return new FloatingActionButton(
......
...@@ -22,7 +22,19 @@ class FitnessApp extends App { ...@@ -22,7 +22,19 @@ class FitnessApp extends App {
_navigationState = new NavigationState([ _navigationState = new NavigationState([
new Route( new Route(
name: '/', name: '/',
builder: (navigator, route) => new HomeFragment(navigator, _userData) builder: (navigator, route) => new HomeFragment(
navigator: navigator,
userData: _userData,
onMeasurementCreated: _handleMeasurementCreated,
onMeasurementDeleted: _handleMeasurementDeleted
)
),
new Route(
name: '/measurements/new',
builder: (navigator, route) => new MeasurementFragment(
navigator: navigator,
onCreated: _handleMeasurementCreated
)
), ),
new Route( new Route(
name: '/settings', name: '/settings',
...@@ -42,6 +54,19 @@ class FitnessApp extends App { ...@@ -42,6 +54,19 @@ class FitnessApp extends App {
} }
} }
void _handleMeasurementCreated(Measurement measurement) {
setState(() {
_userData.add(measurement);
_userData.sort((a, b) => a.when.compareTo(b.when));
});
}
void _handleMeasurementDeleted(Measurement measurement) {
setState(() {
_userData.remove(measurement);
});
}
BackupMode backupSetting = BackupMode.disabled; BackupMode backupSetting = BackupMode.disabled;
void settingsUpdater({ BackupMode backup }) { void settingsUpdater({ BackupMode backup }) {
...@@ -52,7 +77,8 @@ class FitnessApp extends App { ...@@ -52,7 +77,8 @@ class FitnessApp extends App {
} }
final List<Measurement> _userData = [ final List<Measurement> _userData = [
new Measurement(when: new DateTime.now(), weight: 400.0) new Measurement(weight: 180.0, when: new DateTime.now().add(const Duration(days: -1))),
new Measurement(weight: 160.0, when: new DateTime.now()),
]; ];
Widget build() { Widget build() {
......
...@@ -2,10 +2,112 @@ ...@@ -2,10 +2,112 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/editing/input.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/flat_button.dart';
import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/ink_well.dart';
import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/scrollable_viewport.dart';
import 'package:sky/widgets/snack_bar.dart';
import 'package:sky/widgets/tool_bar.dart';
typedef void MeasurementHandler(Measurement measurement);
class Measurement { class Measurement {
Measurement({ this.when, this.weight });
final DateTime when; final DateTime when;
final double weight; final double weight;
Measurement({ this.when, this.weight }); // TODO(jackson): Internationalize
String get displayWeight => "${weight.toStringAsFixed(2)} lbs";
String get displayDate => "${when.year.toString()}-${when.month.toString().padLeft(2,'0')}-${when.day.toString().padLeft(2,'0')}";
} }
class MeasurementFragment extends StatefulComponent {
MeasurementFragment({ this.navigator, this.onCreated });
Navigator navigator;
MeasurementHandler onCreated;
void syncFields(MeasurementFragment source) {
navigator = source.navigator;
onCreated = source.onCreated;
}
String _weight = "";
String _errorMessage = null;
void _handleSave() {
double parsedWeight;
try {
parsedWeight = double.parse(_weight);
} on FormatException {
setState(() {
_errorMessage = "Save failed";
});
return;
}
onCreated(new Measurement(when: new DateTime.now(), weight: parsedWeight));
navigator.pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: navigator.pop),
center: new Text('New Measurement'),
right: [new InkWell(
child: new Listener(
onGestureTap: (_) => _handleSave(),
child: new Text('SAVE')
)
)]
);
}
void _handleWeightChanged(String weight) {
setState(() {
_weight = weight;
});
}
Widget buildMeasurementPane() {
Measurement measurement = new Measurement(when: new DateTime.now());
return new Material(
type: MaterialType.canvas,
child: new ScrollableViewport(
child: new Container(
padding: const EdgeDims.all(20.0),
child: new Block([
new Text(measurement.displayDate),
new Input(
focused: false,
placeholder: 'Enter weight',
onChanged: _handleWeightChanged
),
])
)
)
);
}
Widget buildSnackBar() {
if (_errorMessage == null)
return null;
return new SnackBar(content: new Text(_errorMessage));
}
Widget build() {
return new Scaffold(
toolbar: buildToolBar(),
body: buildMeasurementPane(),
snackBar: buildSnackBar()
);
}
}
// 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 'dart:sky' as sky;
const kMaxIterations = 100;
int peakCount = 1000; // this is the number that must be reached for us to start reporting the peak number of nodes in the tree each frame
void report(String s) {
// uncomment this if you want to see what the mutations are
// print("$s\n");
}
sky.LayoutRoot layoutRoot = new sky.LayoutRoot();
sky.Document document = new sky.Document();
sky.Element root;
math.Random random = new math.Random();
bool pickThis(double odds) {
return random.nextDouble() < odds;
}
String generateCharacter(int min, int max) {
String result = new String.fromCharCode(random.nextInt(max)+min);
report("generated character: '$result'");
return result;
}
String colorToCSSString(sky.Color color) {
return 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})';
}
void doFrame(double timeStamp) {
// mutate the DOM randomly
int iterationsLeft = kMaxIterations;
sky.Node node = root;
sky.Node other = null;
while (node != null && iterationsLeft > 0) {
iterationsLeft -= 1;
if (node is sky.Element && pickThis(0.4)) {
node = (node as sky.Element).firstChild;
} else if (node.nextSibling != null && pickThis(0.5)) {
node = node.nextSibling;
} else if (other == null && node != root && pickThis(0.1)) {
other = node;
node = root;
} else if (node != root && other != null && pickThis(0.1)) {
report("insertBefore()");
node.insertBefore([other]);
break;
} else if (pickThis(0.001)) {
report("remove()");
node.remove();
} else if (node is sky.Element) {
if (pickThis(0.1)) {
report("appending a new text node (ASCII)");
node.appendChild(document.createText(generateCharacter(0x20, 0x7F)));
break;
} else if (pickThis(0.05)) {
report("appending a new text node (Latin1)");
node.appendChild(document.createText(generateCharacter(0x20, 0xFF)));
break;
} else if (pickThis(0.025)) {
report("appending a new text node (BMP)");
node.appendChild(document.createText(generateCharacter(0x20, 0xFFFF)));
break;
} else if (pickThis(0.0125)) {
report("appending a new text node (Unicode)");
node.appendChild(document.createText(generateCharacter(0x20, 0x10FFFF)));
break;
} else if (pickThis(0.1)) {
report("appending a new Element");
node.appendChild(document.createElement('t'));
break;
} else if (pickThis(0.1)) {
report("styling: color");
node.style['color'] = colorToCSSString(new sky.Color(random.nextInt(0xFFFFFFFF) | 0xC0808080));
break;
} else if (pickThis(0.1)) {
report("styling: font-size");
node.style['font-size'] = "${random.nextDouble() * 48.0}px";
break;
} else if (pickThis(0.2)) {
report("styling: font-weight");
node.style['font-weight'] = "${random.nextInt(8)+1}00";
break;
} else if (pickThis(0.1)) {
report("styling: line-height, dynamic");
node.style['line-height'] = "${random.nextDouble()*1.5}";
break;
} else if (pickThis(0.001)) {
report("styling: line-height, fixed");
node.style['line-height'] = "${random.nextDouble()*1.5}px";
break;
} else if (pickThis(0.025)) {
report("styling: font-style italic");
node.style['font-style'] = "italic";
break;
} else if (pickThis(0.05)) {
report("styling: font-style normal");
node.style['font-style'] = "normal";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration");
node.style['text-decoration-line'] = "none";
break;
} else if (pickThis(0.01)) {
report("styling: text-decoration");
node.style['text-decoration-line'] = "underline";
break;
} else if (pickThis(0.01)) {
report("styling: text-decoration");
node.style['text-decoration-line'] = "overline";
break;
} else if (pickThis(0.01)) {
report("styling: text-decoration");
node.style['text-decoration-line'] = "underline overline";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-style: inherit");
node.style['text-decoration-style'] = "inherit";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-style: solid");
node.style['text-decoration-style'] = "solid";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-style: double");
node.style['text-decoration-style'] = "double";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-style: dotted");
node.style['text-decoration-style'] = "dotted";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-style: dashed");
node.style['text-decoration-style'] = "dashed";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-style: wavy");
node.style['text-decoration-style'] = "wavy";
break;
} else if (pickThis(0.1)) {
report("styling: text-decoration-color");
node.style['text-decoration-color'] = colorToCSSString(new sky.Color(random.nextInt(0xFFFFFFFF)));
break;
}
} else {
assert(node is sky.Text); //
if (pickThis(0.1)) {
report("appending a new text node (ASCII)");
node.appendData(generateCharacter(0x20, 0x7F));
break;
} else if (pickThis(0.05)) {
report("appending a new text node (Latin1)");
node.appendData(generateCharacter(0x20, 0xFF));
break;
} else if (pickThis(0.025)) {
report("appending a new text node (BMP)");
node.appendData(generateCharacter(0x20, 0xFFFF));
break;
} else if (pickThis(0.0125)) {
report("appending a new text node (Unicode)");
node.appendData(generateCharacter(0x20, 0x10FFFF));
break;
} else if (node.length > 1 && pickThis(0.1)) {
report("deleting character from Text node");
node.deleteData(random.nextInt(node.length), 1);
break;
}
}
}
report("counting...");
node = root;
int count = 1;
while (node != null) {
if (node is sky.Element && node.firstChild != null) {
node = node.firstChild;
count += 1;
} else {
while (node != null && node.nextSibling == null)
node = node.parentNode;
if (node != null && node.nextSibling != null) {
node = node.nextSibling;
count += 1;
}
}
}
report("node count: $count\r");
if (count > peakCount) {
peakCount = count;
print("peak node count so far: $count\r");
}
// draw the result
report("recording...");
sky.PictureRecorder recorder = new sky.PictureRecorder();
sky.Canvas canvas = new sky.Canvas(recorder, new sky.Rect.fromLTWH(0.0, 0.0, sky.view.width, sky.view.height));
layoutRoot.maxWidth = sky.view.width;
layoutRoot.layout();
layoutRoot.paint(canvas);
report("painting...");
sky.view.picture = recorder.endRecording();
sky.view.scheduleFrame();
}
void main() {
root = document.createElement('p');
root.style['display'] = 'paragraph';
root.style['color'] = '#FFFFFF';
layoutRoot.rootElement = root;
sky.view.setFrameCallback(doFrame);
sky.view.scheduleFrame();
}
...@@ -20,7 +20,7 @@ void main() { ...@@ -20,7 +20,7 @@ void main() {
TextStyle style = const TextStyle(color: const Color(0xFF000000)); TextStyle style = const TextStyle(color: const Color(0xFF000000));
RenderParagraph paragraph = new RenderParagraph(new InlineStyle(style, [new InlineText("${alignItems}")])); RenderParagraph paragraph = new RenderParagraph(new InlineStyle(style, [new InlineText("${alignItems}")]));
table.add(new RenderPadding(child: paragraph, padding: new EdgeDims.only(top: 20.0))); table.add(new RenderPadding(child: paragraph, padding: new EdgeDims.only(top: 20.0)));
var row = new RenderFlex(alignItems: alignItems); var row = new RenderFlex(alignItems: alignItems, baseline: TextBaseline.alphabetic);
style = new TextStyle(fontSize: 15.0, color: const Color(0xFF000000)); style = new TextStyle(fontSize: 15.0, color: const Color(0xFF000000));
row.add(new RenderDecoratedBox( row.add(new RenderDecoratedBox(
...@@ -32,7 +32,7 @@ void main() { ...@@ -32,7 +32,7 @@ void main() {
decoration: new BoxDecoration(backgroundColor: const Color(0x7FCCFFCC)), decoration: new BoxDecoration(backgroundColor: const Color(0x7FCCFFCC)),
child: new RenderParagraph(new InlineStyle(style, [new InlineText('foo foo foo')])) child: new RenderParagraph(new InlineStyle(style, [new InlineText('foo foo foo')]))
)); ));
var subrow = new RenderFlex(alignItems: alignItems); var subrow = new RenderFlex(alignItems: alignItems, baseline: TextBaseline.alphabetic);
style = new TextStyle(fontSize: 25.0, color: const Color(0xFF000000)); style = new TextStyle(fontSize: 25.0, color: const Color(0xFF000000));
subrow.add(new RenderDecoratedBox( subrow.add(new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0x7FCCCCFF)), decoration: new BoxDecoration(backgroundColor: const Color(0x7FCCCCFF)),
......
...@@ -6,6 +6,7 @@ import 'package:sky/painting/text_style.dart'; ...@@ -6,6 +6,7 @@ import 'package:sky/painting/text_style.dart';
import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/box.dart';
import 'package:sky/widgets/ink_well.dart'; import 'package:sky/widgets/ink_well.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/theme.dart';
import 'stock_arrow.dart'; import 'stock_arrow.dart';
...@@ -60,7 +61,11 @@ class StockRow extends Component { ...@@ -60,7 +61,11 @@ class StockRow extends Component {
margin: const EdgeDims.only(right: 5.0) margin: const EdgeDims.only(right: 5.0)
), ),
new Flexible( new Flexible(
child: new Flex(children, alignItems: FlexAlignItems.baseline) child: new Flex(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
)
) )
]) ])
) )
......
...@@ -15,6 +15,7 @@ import 'package:sky/widgets/widget.dart'; ...@@ -15,6 +15,7 @@ import 'package:sky/widgets/widget.dart';
class BlockViewportApp extends App { class BlockViewportApp extends App {
BlockViewportLayoutState layoutState = new BlockViewportLayoutState();
List<double> lengths = <double>[]; List<double> lengths = <double>[];
double offset = 0.0; double offset = 0.0;
...@@ -96,7 +97,8 @@ class BlockViewportApp extends App { ...@@ -96,7 +97,8 @@ class BlockViewportApp extends App {
child: new BlockViewport( child: new BlockViewport(
builder: builder, builder: builder,
startOffset: offset, startOffset: offset,
token: lengths.length token: lengths.length,
layoutState: layoutState
) )
) )
), ),
......
...@@ -2,62 +2,145 @@ ...@@ -2,62 +2,145 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/base/lerp.dart'; import 'package:sky/base/lerp.dart';
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/colors.dart'; import 'package:sky/theme/colors.dart';
import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/block_viewport.dart';
import 'package:sky/widgets/card.dart'; import 'package:sky/widgets/card.dart';
import 'package:sky/widgets/dismissable.dart'; import 'package:sky/widgets/dismissable.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/variable_height_scrollable.dart'; import 'package:sky/widgets/variable_height_scrollable.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart'; import 'package:sky/widgets/tool_bar.dart';
import 'package:sky/widgets/widget.dart'; import 'package:sky/widgets/widget.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/task_description.dart'; import 'package:sky/widgets/task_description.dart';
class CardModel {
CardModel(this.value, this.height, this.color);
int value;
double height;
Color color;
AnimationPerformance performance;
String get label => "Item $value";
String get key => value.toString();
bool operator ==(other) => other is CardModel && other.value == value;
int get hashCode => 373 * 37 * value.hashCode;
}
class ShrinkingCard extends AnimatedComponent {
ShrinkingCard({
String key,
CardModel this.card,
Function this.onUpdated,
Function this.onCompleted
}) : super(key: key);
CardModel card;
Function onUpdated;
Function onCompleted;
double get currentHeight => card.performance.variable.value;
void initState() {
assert(card.performance != null);
card.performance.addListener(handleAnimationProgress);
watch(card.performance);
}
void handleAnimationProgress() {
if (card.performance.isCompleted) {
if (onCompleted != null)
onCompleted();
} else if (onUpdated != null) {
onUpdated();
}
}
void syncFields(ShrinkingCard source) {
card = source.card;
onCompleted = source.onCompleted;
onUpdated = source.onUpdated;
super.syncFields(source);
}
Widget build() => new Container(height: currentHeight);
}
class CardCollectionApp extends App { class CardCollectionApp extends App {
final TextStyle cardLabelStyle = final TextStyle cardLabelStyle =
new TextStyle(color: white, fontSize: 18.0, fontWeight: bold); new TextStyle(color: white, fontSize: 18.0, fontWeight: bold);
final List<double> cardHeights = [ BlockViewportLayoutState layoutState = new BlockViewportLayoutState();
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, List<CardModel> cardModels;
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0
];
List<int> visibleCardIndices;
void initState() { void initState() {
visibleCardIndices = new List.generate(cardHeights.length, (i) => i); List<double> cardHeights = <double>[
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0
];
cardModels = new List.generate(cardHeights.length, (i) {
Color color = lerpColor(Red[300], Blue[900], i / cardHeights.length);
return new CardModel(i, cardHeights[i], color);
});
super.initState(); super.initState();
} }
void dismissCard(int cardIndex) { void shrinkCard(CardModel card, int index) {
if (card.performance != null)
return;
layoutState.invalidate([index]);
setState(() { setState(() {
visibleCardIndices.remove(cardIndex); assert(card.performance == null);
card.performance = new AnimationPerformance()
..duration = const Duration(milliseconds: 300)
..variable = new AnimatedType<double>(
card.height + kCardMargins.top + kCardMargins.bottom,
end: 0.0,
curve: ease,
interval: new Interval(0.5, 1.0)
)
..play();
}); });
} }
Widget _builder(int index) { void dismissCard(CardModel card) {
if (index >= visibleCardIndices.length) if (cardModels.contains(card)) {
setState(() {
cardModels.remove(card);
});
}
}
Widget builder(int index) {
if (index >= cardModels.length)
return null; return null;
CardModel card = cardModels[index];
if (card.performance != null) {
return new ShrinkingCard(
key: card.key,
card: card,
onUpdated: () { layoutState.invalidate([index]); },
onCompleted: () { dismissCard(card); }
);
}
int cardIndex = visibleCardIndices[index];
Color color = lerpColor(Red[500], Blue[500], cardIndex / cardHeights.length);
Widget label = new Text("Item ${cardIndex}", style: cardLabelStyle);
return new Dismissable( return new Dismissable(
key: cardIndex.toString(), key: card.key,
onDismissed: () { dismissCard(cardIndex); }, onDismissed: () { shrinkCard(card, index); },
child: new Card( child: new Card(
color: color, color: card.color,
child: new Container( child: new Container(
height: cardHeights[cardIndex], height: card.height,
padding: const EdgeDims.all(8.0), padding: const EdgeDims.all(8.0),
child: new Center(child: label) child: new Center(child: new Text(card.label, style: cardLabelStyle))
) )
) )
); );
...@@ -68,16 +151,17 @@ class CardCollectionApp extends App { ...@@ -68,16 +151,17 @@ class CardCollectionApp extends App {
padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0),
decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]), decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]),
child: new VariableHeightScrollable( child: new VariableHeightScrollable(
builder: _builder, builder: builder,
token: visibleCardIndices.length token: cardModels.length,
layoutState: layoutState
) )
); );
return new Theme( return new Theme(
data: new ThemeData( data: new ThemeData(
brightness: ThemeBrightness.light, brightness: ThemeBrightness.light,
primarySwatch: colors.Blue, primarySwatch: Blue,
accentColor: colors.RedAccent[200] accentColor: RedAccent[200]
), ),
child: new TaskDescription( child: new TaskDescription(
label: 'Cards', label: 'Cards',
......
...@@ -10,6 +10,8 @@ const bold = FontWeight.w700; ...@@ -10,6 +10,8 @@ const bold = FontWeight.w700;
enum TextAlign { left, right, center } enum TextAlign { left, right, center }
enum TextBaseline { alphabetic, ideographic }
enum TextDecoration { none, underline, overline, lineThrough } enum TextDecoration { none, underline, overline, lineThrough }
const underline = const <TextDecoration>[TextDecoration.underline]; const underline = const <TextDecoration>[TextDecoration.underline];
const overline = const <TextDecoration>[TextDecoration.overline]; const overline = const <TextDecoration>[TextDecoration.overline];
...@@ -24,6 +26,7 @@ class TextStyle { ...@@ -24,6 +26,7 @@ class TextStyle {
this.fontSize, this.fontSize,
this.fontWeight, this.fontWeight,
this.textAlign, this.textAlign,
this.textBaseline,
this.height, this.height,
this.decoration, this.decoration,
this.decorationColor, this.decorationColor,
...@@ -35,6 +38,7 @@ class TextStyle { ...@@ -35,6 +38,7 @@ class TextStyle {
final double fontSize; // in pixels final double fontSize; // in pixels
final FontWeight fontWeight; final FontWeight fontWeight;
final TextAlign textAlign; final TextAlign textAlign;
final TextBaseline textBaseline;
final double height; // multiple of fontSize final double height; // multiple of fontSize
final List<TextDecoration> decoration; // TODO(ianh): Switch this to a Set<> once Dart supports constant Sets final List<TextDecoration> decoration; // TODO(ianh): Switch this to a Set<> once Dart supports constant Sets
final Color decorationColor; final Color decorationColor;
...@@ -46,6 +50,7 @@ class TextStyle { ...@@ -46,6 +50,7 @@ class TextStyle {
double fontSize, double fontSize,
FontWeight fontWeight, FontWeight fontWeight,
TextAlign textAlign, TextAlign textAlign,
TextBaseline textBaseline,
double height, double height,
List<TextDecoration> decoration, List<TextDecoration> decoration,
Color decorationColor, Color decorationColor,
...@@ -57,6 +62,7 @@ class TextStyle { ...@@ -57,6 +62,7 @@ class TextStyle {
fontSize: fontSize != null ? fontSize : this.fontSize, fontSize: fontSize != null ? fontSize : this.fontSize,
fontWeight: fontWeight != null ? fontWeight : this.fontWeight, fontWeight: fontWeight != null ? fontWeight : this.fontWeight,
textAlign: textAlign != null ? textAlign : this.textAlign, textAlign: textAlign != null ? textAlign : this.textAlign,
textBaseline: textBaseline != null ? textBaseline : this.textBaseline,
height: height != null ? height : this.height, height: height != null ? height : this.height,
decoration: decoration != null ? decoration : this.decoration, decoration: decoration != null ? decoration : this.decoration,
decorationColor: decorationColor != null ? decorationColor : this.decorationColor, decorationColor: decorationColor != null ? decorationColor : this.decorationColor,
...@@ -71,6 +77,7 @@ class TextStyle { ...@@ -71,6 +77,7 @@ class TextStyle {
fontSize: other.fontSize, fontSize: other.fontSize,
fontWeight: other.fontWeight, fontWeight: other.fontWeight,
textAlign: other.textAlign, textAlign: other.textAlign,
textBaseline: other.textBaseline,
height: other.height, height: other.height,
decoration: other.decoration, decoration: other.decoration,
decorationColor: other.decorationColor, decorationColor: other.decorationColor,
...@@ -157,10 +164,11 @@ class TextStyle { ...@@ -157,10 +164,11 @@ class TextStyle {
return true; return true;
return other is TextStyle && return other is TextStyle &&
color == other.color && color == other.color &&
fontFamily == other.fontFamily && fontFamily == other.fontFamily &&
fontSize == other.fontSize && fontSize == other.fontSize &&
fontWeight == other.fontWeight && fontWeight == other.fontWeight &&
textAlign == other.textAlign && textAlign == other.textAlign &&
textBaseline == other.textBaseline &&
decoration == other.decoration && decoration == other.decoration &&
decorationColor == other.decorationColor && decorationColor == other.decorationColor &&
decorationStyle == other.decorationStyle; decorationStyle == other.decorationStyle;
...@@ -174,6 +182,7 @@ class TextStyle { ...@@ -174,6 +182,7 @@ class TextStyle {
value = 37 * value + fontSize.hashCode; value = 37 * value + fontSize.hashCode;
value = 37 * value + fontWeight.hashCode; value = 37 * value + fontWeight.hashCode;
value = 37 * value + textAlign.hashCode; value = 37 * value + textAlign.hashCode;
value = 37 * value + textBaseline.hashCode;
value = 37 * value + decoration.hashCode; value = 37 * value + decoration.hashCode;
value = 37 * value + decorationColor.hashCode; value = 37 * value + decorationColor.hashCode;
value = 37 * value + decorationStyle.hashCode; value = 37 * value + decorationStyle.hashCode;
...@@ -193,6 +202,8 @@ class TextStyle { ...@@ -193,6 +202,8 @@ class TextStyle {
result.add('${prefix}fontWeight: $fontWeight'); result.add('${prefix}fontWeight: $fontWeight');
if (textAlign != null) if (textAlign != null)
result.add('${prefix}textAlign: $textAlign'); result.add('${prefix}textAlign: $textAlign');
if (textBaseline != null)
result.add('${prefix}textBaseline: $textBaseline');
if (decoration != null) if (decoration != null)
result.add('${prefix}decoration: $decoration'); result.add('${prefix}decoration: $decoration');
if (decorationColor != null) if (decorationColor != null)
......
...@@ -7,10 +7,12 @@ import 'dart:sky' as sky; ...@@ -7,10 +7,12 @@ import 'dart:sky' as sky;
import 'package:sky/base/debug.dart'; import 'package:sky/base/debug.dart';
import 'package:sky/painting/box_painter.dart'; import 'package:sky/painting/box_painter.dart';
import 'package:sky/painting/text_style.dart';
import 'package:sky/rendering/object.dart'; import 'package:sky/rendering/object.dart';
import 'package:vector_math/vector_math.dart'; import 'package:vector_math/vector_math.dart';
export 'package:sky/painting/box_painter.dart'; export 'package:sky/painting/box_painter.dart';
export 'package:sky/painting/text_style.dart' show TextBaseline;
// GENERIC BOX RENDERING // GENERIC BOX RENDERING
// Anything that has a concept of x, y, width, height is going to derive from this // Anything that has a concept of x, y, width, height is going to derive from this
...@@ -253,8 +255,6 @@ class BoxParentData extends ParentData { ...@@ -253,8 +255,6 @@ class BoxParentData extends ParentData {
String toString() => 'position=$position'; String toString() => 'position=$position';
} }
enum TextBaseline { alphabetic, ideographic }
abstract class RenderBox extends RenderObject { abstract class RenderBox extends RenderObject {
void setupParentData(RenderObject child) { void setupParentData(RenderObject child) {
...@@ -1296,7 +1296,7 @@ class RenderImage extends RenderBox { ...@@ -1296,7 +1296,7 @@ class RenderImage extends RenderBox {
return _cachedPaint; return _cachedPaint;
} }
Size _sizeForConstraints(BoxConstraints innerConstraints) { Size _sizeForConstraints(BoxConstraints constraints) {
// If there's no image, we can't size ourselves automatically // If there's no image, we can't size ourselves automatically
if (_image == null) { if (_image == null) {
double width = requestedSize.width == null ? 0.0 : requestedSize.width; double width = requestedSize.width == null ? 0.0 : requestedSize.width;
...@@ -1304,7 +1304,7 @@ class RenderImage extends RenderBox { ...@@ -1304,7 +1304,7 @@ class RenderImage extends RenderBox {
return constraints.constrain(new Size(width, height)); return constraints.constrain(new Size(width, height));
} }
if (!innerConstraints.isTight) { if (!constraints.isTight) {
// If neither height nor width are specified, use inherent image // If neither height nor width are specified, use inherent image
// dimensions. If only one dimension is specified, adjust the // dimensions. If only one dimension is specified, adjust the
// other dimension to maintain the aspect ratio. In both cases, // other dimension to maintain the aspect ratio. In both cases,
......
...@@ -47,10 +47,12 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -47,10 +47,12 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
List<RenderBox> children, List<RenderBox> children,
FlexDirection direction: FlexDirection.horizontal, FlexDirection direction: FlexDirection.horizontal,
FlexJustifyContent justifyContent: FlexJustifyContent.start, FlexJustifyContent justifyContent: FlexJustifyContent.start,
FlexAlignItems alignItems: FlexAlignItems.center FlexAlignItems alignItems: FlexAlignItems.center,
TextBaseline textBaseline
}) : _direction = direction, }) : _direction = direction,
_justifyContent = justifyContent, _justifyContent = justifyContent,
_alignItems = alignItems { _alignItems = alignItems,
_textBaseline = textBaseline {
addAll(children); addAll(children);
} }
...@@ -81,7 +83,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -81,7 +83,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
} }
} }
bool _overflowOccurredDuringLayout = false; TextBaseline _textBaseline;
TextBaseline get textBaseline => _textBaseline;
void set textBaseline (TextBaseline value) {
if (_textBaseline != value) {
_textBaseline = value;
markNeedsLayout();
}
}
// Set during layout if overflow occurred on the main axis
double _overflow;
void setupParentData(RenderBox child) { void setupParentData(RenderBox child) {
if (child.parentData is! FlexBoxParentData) if (child.parentData is! FlexBoxParentData)
...@@ -314,6 +326,8 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -314,6 +326,8 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
} }
child = child.parentData.nextSibling; child = child.parentData.nextSibling;
} }
_overflow = math.max(0.0, -freeSpace);
freeSpace = math.max(0.0, freeSpace);
// Steps 4-5. Distribute remaining space to flexible children. // Steps 4-5. Distribute remaining space to flexible children.
double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0; double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
...@@ -342,8 +356,8 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -342,8 +356,8 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
crossSize = math.max(crossSize, _getCrossSize(child)); crossSize = math.max(crossSize, _getCrossSize(child));
} }
if (alignItems == FlexAlignItems.baseline) { if (alignItems == FlexAlignItems.baseline) {
// TODO(jackson): Support for non-alphabetic baselines assert(textBaseline != null);
double distance = child.getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true); double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
if (distance != null) if (distance != null)
maxBaselineDistance = math.max(maxBaselineDistance, distance); maxBaselineDistance = math.max(maxBaselineDistance, distance);
} }
...@@ -351,7 +365,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -351,7 +365,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
child = child.parentData.nextSibling; child = child.parentData.nextSibling;
} }
// Section 8.2: Axis Alignment using the justify-content property // Section 8.2: Main Axis Alignment using the justify-content property
double remainingSpace = math.max(0.0, freeSpace - usedSpace); double remainingSpace = math.max(0.0, freeSpace - usedSpace);
double leadingSpace; double leadingSpace;
double betweenSpace; double betweenSpace;
...@@ -378,20 +392,16 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -378,20 +392,16 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
break; break;
} }
Size desiredSize;
switch (_direction) { switch (_direction) {
case FlexDirection.horizontal: case FlexDirection.horizontal:
desiredSize = new Size(mainSize, crossSize); size = constraints.constrain(new Size(mainSize, crossSize));
size = constraints.constrain(desiredSize);
crossSize = size.height; crossSize = size.height;
break; break;
case FlexDirection.vertical: case FlexDirection.vertical:
desiredSize = new Size(crossSize, mainSize);
size = constraints.constrain(new Size(crossSize, mainSize)); size = constraints.constrain(new Size(crossSize, mainSize));
crossSize = size.width; crossSize = size.width;
break; break;
} }
_overflowOccurredDuringLayout = desiredSize != size;
// Position elements // Position elements
double childMainPosition = leadingSpace; double childMainPosition = leadingSpace;
...@@ -412,10 +422,9 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -412,10 +422,9 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
break; break;
case FlexAlignItems.baseline: case FlexAlignItems.baseline:
childCrossPosition = 0.0; childCrossPosition = 0.0;
// TODO(jackson): Support for vertical baselines
if (_direction == FlexDirection.horizontal) { if (_direction == FlexDirection.horizontal) {
// TODO(jackson): Support for non-alphabetic baselines assert(textBaseline != null);
double distance = child.getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true); double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
if (distance != null) if (distance != null)
childCrossPosition = maxBaselineDistance - distance; childCrossPosition = maxBaselineDistance - distance;
} }
...@@ -439,7 +448,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -439,7 +448,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
} }
void paint(PaintingCanvas canvas, Offset offset) { void paint(PaintingCanvas canvas, Offset offset) {
if (_overflowOccurredDuringLayout) { if (_overflow > 0) {
canvas.save(); canvas.save();
canvas.clipRect(offset & size); canvas.clipRect(offset & size);
defaultPaint(canvas, offset); defaultPaint(canvas, offset);
...@@ -448,4 +457,26 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -448,4 +457,26 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
defaultPaint(canvas, offset); defaultPaint(canvas, offset);
} }
} }
void debugPaintSize(PaintingCanvas canvas, Offset offset) {
super.debugPaintSize(canvas, offset);
if (_overflow <= 0)
return;
// Draw a red rectangle over the overflow area in debug mode
// You should be using a Clip if you want to clip your children
Paint paint = new Paint()..color = const Color(0x7FFF0000);
Rect overflowRect;
switch(direction) {
case FlexDirection.horizontal:
overflowRect = offset + new Offset(size.width, 0.0) &
new Size(_overflow, size.height);
break;
case FlexDirection.vertical:
overflowRect = offset + new Offset(0.0, size.height) &
new Size(size.width, _overflow);
break;
}
canvas.drawRect(overflowRect, paint);
}
} }
...@@ -9,20 +9,21 @@ import 'dart:sky'; ...@@ -9,20 +9,21 @@ import 'dart:sky';
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
// TODO(eseidel): Font weights are supposed to be language relative! // TODO(eseidel): Font weights are supposed to be language relative!
// TODO(jackson): Baseline should be language relative!
// These values are for English-like text. // These values are for English-like text.
class TextTheme { class TextTheme {
TextTheme._(Color color54, Color color87) TextTheme._(Color color54, Color color87)
: display4 = new TextStyle(fontSize: 112.0, fontWeight: FontWeight.w100, color: color54), : display4 = new TextStyle(fontSize: 112.0, fontWeight: FontWeight.w100, color: color54, textBaseline: TextBaseline.alphabetic),
display3 = new TextStyle(fontSize: 56.0, fontWeight: FontWeight.w400, color: color54), display3 = new TextStyle(fontSize: 56.0, fontWeight: FontWeight.w400, color: color54, textBaseline: TextBaseline.alphabetic),
display2 = new TextStyle(fontSize: 45.0, fontWeight: FontWeight.w400, color: color54, height: 48.0 / 45.0), display2 = new TextStyle(fontSize: 45.0, fontWeight: FontWeight.w400, color: color54, height: 48.0 / 45.0, textBaseline: TextBaseline.alphabetic),
display1 = new TextStyle(fontSize: 34.0, fontWeight: FontWeight.w400, color: color54, height: 40.0 / 34.0), display1 = new TextStyle(fontSize: 34.0, fontWeight: FontWeight.w400, color: color54, height: 40.0 / 34.0, textBaseline: TextBaseline.alphabetic),
headline = new TextStyle(fontSize: 24.0, fontWeight: FontWeight.w400, color: color87, height: 32.0 / 24.0), headline = new TextStyle(fontSize: 24.0, fontWeight: FontWeight.w400, color: color87, height: 32.0 / 24.0, textBaseline: TextBaseline.alphabetic),
title = new TextStyle(fontSize: 20.0, fontWeight: FontWeight.w500, color: color87, height: 28.0 / 20.0), title = new TextStyle(fontSize: 20.0, fontWeight: FontWeight.w500, color: color87, height: 28.0 / 20.0, textBaseline: TextBaseline.alphabetic),
subhead = new TextStyle(fontSize: 16.0, fontWeight: FontWeight.w400, color: color87, height: 24.0 / 16.0), subhead = new TextStyle(fontSize: 16.0, fontWeight: FontWeight.w400, color: color87, height: 24.0 / 16.0, textBaseline: TextBaseline.alphabetic),
body2 = new TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500, color: color87, height: 24.0 / 14.0), body2 = new TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500, color: color87, height: 24.0 / 14.0, textBaseline: TextBaseline.alphabetic),
body1 = new TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400, color: color87, height: 20.0 / 14.0), body1 = new TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400, color: color87, height: 20.0 / 14.0, textBaseline: TextBaseline.alphabetic),
caption = new TextStyle(fontSize: 12.0, fontWeight: FontWeight.w400, color: color54), caption = new TextStyle(fontSize: 12.0, fontWeight: FontWeight.w400, color: color54, textBaseline: TextBaseline.alphabetic),
button = new TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500, color: color87); button = new TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500, color: color87, textBaseline: TextBaseline.alphabetic);
final TextStyle display4; final TextStyle display4;
final TextStyle display3; final TextStyle display3;
......
...@@ -401,12 +401,14 @@ class Flex extends MultiChildRenderObjectWrapper { ...@@ -401,12 +401,14 @@ class Flex extends MultiChildRenderObjectWrapper {
String key, String key,
this.direction: FlexDirection.horizontal, this.direction: FlexDirection.horizontal,
this.justifyContent: FlexJustifyContent.start, this.justifyContent: FlexJustifyContent.start,
this.alignItems: FlexAlignItems.center this.alignItems: FlexAlignItems.center,
this.textBaseline
}) : super(key: key, children: children); }) : super(key: key, children: children);
final FlexDirection direction; final FlexDirection direction;
final FlexJustifyContent justifyContent; final FlexJustifyContent justifyContent;
final FlexAlignItems alignItems; final FlexAlignItems alignItems;
final TextBaseline textBaseline;
RenderFlex createNode() => new RenderFlex(direction: this.direction); RenderFlex createNode() => new RenderFlex(direction: this.direction);
RenderFlex get root => super.root; RenderFlex get root => super.root;
...@@ -416,6 +418,7 @@ class Flex extends MultiChildRenderObjectWrapper { ...@@ -416,6 +418,7 @@ class Flex extends MultiChildRenderObjectWrapper {
root.direction = direction; root.direction = direction;
root.justifyContent = justifyContent; root.justifyContent = justifyContent;
root.alignItems = alignItems; root.alignItems = alignItems;
root.textBaseline = textBaseline;
} }
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/material.dart'; import 'package:sky/widgets/material.dart';
const EdgeDims kCardMargins = const EdgeDims.all(4.0);
/// A material design card /// A material design card
/// ///
/// <https://www.google.com/design/spec/components/cards.html> /// <https://www.google.com/design/spec/components/cards.html>
...@@ -16,7 +18,7 @@ class Card extends Component { ...@@ -16,7 +18,7 @@ class Card extends Component {
Widget build() { Widget build() {
return new Container( return new Container(
margin: const EdgeDims.all(4.0), margin: kCardMargins,
child: new Material( child: new Material(
color: color, color: color,
type: MaterialType.card, type: MaterialType.card,
......
...@@ -10,7 +10,7 @@ import 'package:sky/widgets/basic.dart'; ...@@ -10,7 +10,7 @@ import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/widget.dart'; import 'package:sky/widgets/widget.dart';
import 'package:vector_math/vector_math.dart'; import 'package:vector_math/vector_math.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 300); const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
const double _kMinFlingVelocity = 700.0; const double _kMinFlingVelocity = 700.0;
const double _kMinFlingVelocityDelta = 400.0; const double _kMinFlingVelocityDelta = 400.0;
const double _kFlingVelocityScale = 1.0/300.0; const double _kFlingVelocityScale = 1.0/300.0;
...@@ -64,6 +64,8 @@ class Dismissable extends AnimatedComponent { ...@@ -64,6 +64,8 @@ class Dismissable extends AnimatedComponent {
} }
void _maybeCallOnDismissed() { void _maybeCallOnDismissed() {
_performance.stop();
_performance.removeListener(_handleAnimationProgressChanged);
if (onDismissed != null) if (onDismissed != null)
onDismissed(); onDismissed();
} }
...@@ -112,7 +114,7 @@ class Dismissable extends AnimatedComponent { ...@@ -112,7 +114,7 @@ class Dismissable extends AnimatedComponent {
if (_performance.isCompleted) if (_performance.isCompleted)
_maybeCallOnDismissed(); _maybeCallOnDismissed();
else if (!_performance.isAnimating) else if (!_performance.isAnimating)
_performance.progress = 0.0; _performance.reverse();
}); });
} }
......
...@@ -53,7 +53,9 @@ class SnackBar extends Component { ...@@ -53,7 +53,9 @@ class SnackBar extends Component {
) )
) )
) )
]..addAll(actions); ];
if (actions != null)
children.addAll(actions);
return new Material( return new Material(
level: 2, level: 2,
color: const Color(0xFF323232), color: const Color(0xFF323232),
......
...@@ -31,12 +31,14 @@ class ToolBar extends Component { ...@@ -31,12 +31,14 @@ class ToolBar extends Component {
Widget build() { Widget build() {
Color toolbarColor = backgroundColor; Color toolbarColor = backgroundColor;
IconThemeData iconThemeData; IconThemeData iconThemeData;
TextStyle defaultTextStyle = typography.white.title; TextStyle centerStyle = typography.white.title;
TextStyle sideStyle = typography.white.body1;
if (toolbarColor == null) { if (toolbarColor == null) {
ThemeData themeData = Theme.of(this); ThemeData themeData = Theme.of(this);
toolbarColor = themeData.primaryColor; toolbarColor = themeData.primaryColor;
if (themeData.primaryColorBrightness == ThemeBrightness.light) { if (themeData.primaryColorBrightness == ThemeBrightness.light) {
defaultTextStyle = typography.black.title; centerStyle = typography.black.title;
sideStyle = typography.black.body2;
iconThemeData = const IconThemeData(color: IconThemeColor.black); iconThemeData = const IconThemeData(color: IconThemeColor.black);
} else { } else {
iconThemeData = const IconThemeData(color: IconThemeColor.white); iconThemeData = const IconThemeData(color: IconThemeColor.white);
...@@ -50,7 +52,7 @@ class ToolBar extends Component { ...@@ -50,7 +52,7 @@ class ToolBar extends Component {
children.add( children.add(
new Flexible( new Flexible(
child: new Padding( child: new Padding(
child: center, child: new DefaultTextStyle(child: center, style: centerStyle),
padding: new EdgeDims.only(left: 24.0) padding: new EdgeDims.only(left: 24.0)
) )
) )
...@@ -61,7 +63,7 @@ class ToolBar extends Component { ...@@ -61,7 +63,7 @@ class ToolBar extends Component {
Widget content = new Container( Widget content = new Container(
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: defaultTextStyle, style: sideStyle,
child: new Flex( child: new Flex(
[new Container(child: new Flex(children), height: kToolBarHeight)], [new Container(child: new Flex(children), height: kToolBarHeight)],
alignItems: FlexAlignItems.end alignItems: FlexAlignItems.end
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:collection';
import 'package:sky/animation/scroll_behavior.dart'; import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/block_viewport.dart'; import 'package:sky/widgets/block_viewport.dart';
...@@ -14,18 +12,47 @@ class VariableHeightScrollable extends Scrollable { ...@@ -14,18 +12,47 @@ class VariableHeightScrollable extends Scrollable {
VariableHeightScrollable({ VariableHeightScrollable({
String key, String key,
this.builder, this.builder,
this.token this.token,
this.layoutState
}) : super(key: key); }) : super(key: key);
IndexedBuilder builder; IndexedBuilder builder;
Object token; Object token;
BlockViewportLayoutState layoutState;
// When the token changes the scrollable's contents may have
// changed. Remember as much so that after the new contents
// have been laid out we can adjust the scrollOffset so that
// the last page of content is still visible.
bool _contentsChanged = true; bool _contentsChanged = true;
void initState() {
assert(layoutState != null);
super.initState();
}
void didMount() {
layoutState.addListener(_handleLayoutChanged);
super.didMount();
}
void didUnmount() {
layoutState.removeListener(_handleLayoutChanged);
super.didUnmount();
}
void syncFields(VariableHeightScrollable source) { void syncFields(VariableHeightScrollable source) {
builder = source.builder; builder = source.builder;
if (token != source.token) if (token != source.token)
_contentsChanged = true; _contentsChanged = true;
token = source.token; token = source.token;
if (layoutState != source.layoutState) {
// Warning: this is unlikely to be what you intended.
assert(source.layoutState != null);
layoutState.removeListener(_handleLayoutChanged);
layoutState = source.layoutState;
layoutState.addListener(_handleLayoutChanged);
}
super.syncFields(source); super.syncFields(source);
} }
...@@ -36,15 +63,9 @@ class VariableHeightScrollable extends Scrollable { ...@@ -36,15 +63,9 @@ class VariableHeightScrollable extends Scrollable {
scrollBehavior.containerSize = newSize.height; scrollBehavior.containerSize = newSize.height;
} }
void _handleLayoutChanged( void _handleLayoutChanged() {
int firstVisibleChildIndex, if (layoutState.didReachLastChild) {
int visibleChildCount, scrollBehavior.contentsSize = layoutState.contentsSize;
UnmodifiableListView<double> childOffsets,
bool didReachLastChild
) {
assert(childOffsets.length > 0);
if (didReachLastChild) {
scrollBehavior.contentsSize = childOffsets.last;
if (_contentsChanged && scrollOffset > scrollBehavior.maxScrollOffset) { if (_contentsChanged && scrollOffset > scrollBehavior.maxScrollOffset) {
_contentsChanged = false; _contentsChanged = false;
settleScrollOffset(); settleScrollOffset();
...@@ -59,7 +80,7 @@ class VariableHeightScrollable extends Scrollable { ...@@ -59,7 +80,7 @@ class VariableHeightScrollable extends Scrollable {
callback: _handleSizeChanged, callback: _handleSizeChanged,
child: new BlockViewport( child: new BlockViewport(
builder: builder, builder: builder,
onLayoutChanged: _handleLayoutChanged, layoutState: layoutState,
startOffset: scrollOffset, startOffset: scrollOffset,
token: token token: token
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment