Commit e525943d authored by Adam Barth's avatar Adam Barth

Add dartdoc for the painting code

parent bbb29f22
...@@ -10,22 +10,40 @@ import 'package:sky/base/image_resource.dart'; ...@@ -10,22 +10,40 @@ import 'package:sky/base/image_resource.dart';
import 'package:sky/base/lerp.dart'; import 'package:sky/base/lerp.dart';
import 'package:sky/painting/shadows.dart'; import 'package:sky/painting/shadows.dart';
/// An immutable set of offsets in each of the four cardinal directions
///
/// Typically used for an offset from each of the four sides of a box. For
/// example, the padding inside a box can be represented using this class.
class EdgeDims { class EdgeDims {
// used for e.g. padding // TODO(abarth): Remove this constructor or rename it to EdgeDims.fromTRBL.
/// Constructs an EdgeDims from offsets from the top, right, bottom and left
const EdgeDims(this.top, this.right, this.bottom, this.left); const EdgeDims(this.top, this.right, this.bottom, this.left);
/// Constructs an EdgeDims where all the offsets are value
const EdgeDims.all(double value) const EdgeDims.all(double value)
: top = value, right = value, bottom = value, left = value; : top = value, right = value, bottom = value, left = value;
/// Constructs an EdgeDims with only the given values non-zero
const EdgeDims.only({ this.top: 0.0, const EdgeDims.only({ this.top: 0.0,
this.right: 0.0, this.right: 0.0,
this.bottom: 0.0, this.bottom: 0.0,
this.left: 0.0 }); this.left: 0.0 });
/// Constructs an EdgeDims with symmetrical vertical and horizontal offsets
const EdgeDims.symmetric({ double vertical: 0.0, const EdgeDims.symmetric({ double vertical: 0.0,
double horizontal: 0.0 }) double horizontal: 0.0 })
: top = vertical, left = horizontal, bottom = vertical, right = horizontal; : top = vertical, left = horizontal, bottom = vertical, right = horizontal;
/// The offset from the top
final double top; final double top;
/// The offset from the right
final double right; final double right;
/// The offset from the bottom
final double bottom; final double bottom;
/// The offset from the left
final double left; final double left;
bool operator ==(other) { bool operator ==(other) {
...@@ -52,6 +70,7 @@ class EdgeDims { ...@@ -52,6 +70,7 @@ class EdgeDims {
left - other.left); left - other.left);
} }
/// An EdgeDims with zero offsets in each direction
static const EdgeDims zero = const EdgeDims(0.0, 0.0, 0.0, 0.0); static const EdgeDims zero = const EdgeDims(0.0, 0.0, 0.0, 0.0);
int get hashCode { int get hashCode {
...@@ -65,14 +84,20 @@ class EdgeDims { ...@@ -65,14 +84,20 @@ class EdgeDims {
String toString() => "EdgeDims($top, $right, $bottom, $left)"; String toString() => "EdgeDims($top, $right, $bottom, $left)";
} }
/// A side of a border of a box
class BorderSide { class BorderSide {
const BorderSide({ const BorderSide({
this.color: const Color(0xFF000000), this.color: const Color(0xFF000000),
this.width: 1.0 this.width: 1.0
}); });
/// The color of this side of the border
final Color color; final Color color;
/// The width of this side of the border
final double width; final double width;
/// A black border side of zero width
static const none = const BorderSide(width: 0.0); static const none = const BorderSide(width: 0.0);
int get hashCode { int get hashCode {
...@@ -84,6 +109,7 @@ class BorderSide { ...@@ -84,6 +109,7 @@ class BorderSide {
String toString() => 'BorderSide($color, $width)'; String toString() => 'BorderSide($color, $width)';
} }
/// A border of a box, comprised of four sides
class Border { class Border {
const Border({ const Border({
this.top: BorderSide.none, this.top: BorderSide.none,
...@@ -92,6 +118,7 @@ class Border { ...@@ -92,6 +118,7 @@ class Border {
this.left: BorderSide.none this.left: BorderSide.none
}); });
/// A uniform border with all sides the same color and width
factory Border.all({ factory Border.all({
Color color: const Color(0xFF000000), Color color: const Color(0xFF000000),
double width: 1.0 double width: 1.0
...@@ -100,11 +127,19 @@ class Border { ...@@ -100,11 +127,19 @@ class Border {
return new Border(top: side, right: side, bottom: side, left: side); return new Border(top: side, right: side, bottom: side, left: side);
} }
/// The top side of this border
final BorderSide top; final BorderSide top;
/// The right side of this border
final BorderSide right; final BorderSide right;
/// The bottom side of this border
final BorderSide bottom; final BorderSide bottom;
/// The left side of this border
final BorderSide left; final BorderSide left;
/// The widths of the sides of this border represented as an EdgeDims
EdgeDims get dimensions { EdgeDims get dimensions {
return new EdgeDims(top.width, right.width, bottom.width, left.width); return new EdgeDims(top.width, right.width, bottom.width, left.width);
} }
...@@ -120,6 +155,10 @@ class Border { ...@@ -120,6 +155,10 @@ class Border {
String toString() => 'Border($top, $right, $bottom, $left)'; String toString() => 'Border($top, $right, $bottom, $left)';
} }
/// A shadow cast by a box
///
/// Note: BoxShadow can cast non-rectangular shadows if the box is
/// non-rectangular (e.g., has a border radius or a circular shape).
class BoxShadow { class BoxShadow {
const BoxShadow({ const BoxShadow({
this.color, this.color,
...@@ -127,10 +166,16 @@ class BoxShadow { ...@@ -127,10 +166,16 @@ class BoxShadow {
this.blur this.blur
}); });
/// The color of the shadow
final Color color; final Color color;
/// The displacement of the shadow from the box
final Offset offset; final Offset offset;
/// The standard deviation of the Gaussian to convolve with the box's shape
final double blur; final double blur;
/// Returns a new box shadow with its offset and blur scaled by the given factor
BoxShadow scale(double factor) { BoxShadow scale(double factor) {
return new BoxShadow( return new BoxShadow(
color: color, color: color,
...@@ -139,10 +184,12 @@ class BoxShadow { ...@@ -139,10 +184,12 @@ class BoxShadow {
); );
} }
String toString() => 'BoxShadow($color, $offset, $blur)'; /// Linearly interpolate between two box shadows
} ///
/// If either box shadow is null, this function linearly interpolates from a
BoxShadow lerpBoxShadow(BoxShadow a, BoxShadow b, double t) { /// a box shadow that matches the other box shadow in color but has a zero
/// offset and a zero blur.
static BoxShadow lerp(BoxShadow a, BoxShadow b, double t) {
if (a == null && b == null) if (a == null && b == null)
return null; return null;
if (a == null) if (a == null)
...@@ -154,9 +201,12 @@ BoxShadow lerpBoxShadow(BoxShadow a, BoxShadow b, double t) { ...@@ -154,9 +201,12 @@ BoxShadow lerpBoxShadow(BoxShadow a, BoxShadow b, double t) {
offset: lerpOffset(a.offset, b.offset, t), offset: lerpOffset(a.offset, b.offset, t),
blur: lerpNum(a.blur, b.blur, t) blur: lerpNum(a.blur, b.blur, t)
); );
} }
List<BoxShadow> lerpListBoxShadow(List<BoxShadow> a, List<BoxShadow> b, double t) { /// Linearly interpolate between two lists of box shadows
///
/// If the lists differ in length, excess items are lerped with null.
static List<BoxShadow> lerpList(List<BoxShadow> a, List<BoxShadow> b, double t) {
if (a == null && b == null) if (a == null && b == null)
return null; return null;
if (a == null) if (a == null)
...@@ -166,70 +216,140 @@ List<BoxShadow> lerpListBoxShadow(List<BoxShadow> a, List<BoxShadow> b, double t ...@@ -166,70 +216,140 @@ List<BoxShadow> lerpListBoxShadow(List<BoxShadow> a, List<BoxShadow> b, double t
List<BoxShadow> result = new List<BoxShadow>(); List<BoxShadow> result = new List<BoxShadow>();
int commonLength = math.min(a.length, b.length); int commonLength = math.min(a.length, b.length);
for (int i = 0; i < commonLength; ++i) for (int i = 0; i < commonLength; ++i)
result.add(lerpBoxShadow(a[i], b[i], t)); result.add(BoxShadow.lerp(a[i], b[i], t));
for (int i = commonLength; i < a.length; ++i) for (int i = commonLength; i < a.length; ++i)
result.add(a[i].scale(1.0 - t)); result.add(a[i].scale(1.0 - t));
for (int i = commonLength; i < b.length; ++i) for (int i = commonLength; i < b.length; ++i)
result.add(b[i].scale(t)); result.add(b[i].scale(t));
return result; return result;
}
String toString() => 'BoxShadow($color, $offset, $blur)';
} }
/// A 2D gradient
abstract class Gradient { abstract class Gradient {
sky.Shader createShader(); sky.Shader createShader();
} }
/// A 2D linear gradient
class LinearGradient extends Gradient { class LinearGradient extends Gradient {
LinearGradient({ LinearGradient({
this.endPoints, this.begin,
this.end,
this.colors, this.colors,
this.colorStops, this.stops,
this.tileMode: sky.TileMode.clamp this.tileMode: sky.TileMode.clamp
}); }) {
assert(colors.length == stops.length);
}
/// The point at which stop 0.0 of the gradient is placed
final Point begin;
final List<Point> endPoints; /// The point at which stop 1.0 of the gradient is placed
final Point end;
/// The colors the gradient should obtain at each of the stops
///
/// Note: This list must have the same length as [stops].
final List<Color> colors; final List<Color> colors;
final List<double> colorStops;
/// A list of values from 0.0 to 1.0 that denote fractions of the vector from start to end
///
/// Note: This list must have the same length as [colors].
final List<double> stops;
/// How this gradient should tile the plane
final sky.TileMode tileMode; final sky.TileMode tileMode;
sky.Shader createShader() { sky.Shader createShader() {
return new sky.Gradient.linear(this.endPoints, this.colors, return new sky.Gradient.linear([begin, end], this.colors,
this.colorStops, this.tileMode); this.stops, this.tileMode);
} }
String toString() { String toString() {
return 'LinearGradient($endPoints, $colors, $colorStops, $tileMode)'; return 'LinearGradient($begin, $end, $colors, $stops, $tileMode)';
} }
} }
/// A 2D radial gradient
class RadialGradient extends Gradient { class RadialGradient extends Gradient {
RadialGradient({ RadialGradient({
this.center, this.center,
this.radius, this.radius,
this.colors, this.colors,
this.colorStops, this.stops,
this.tileMode: sky.TileMode.clamp this.tileMode: sky.TileMode.clamp
}); });
/// The center of the gradient
final Point center; final Point center;
/// The radius at which stop 1.0 is placed
final double radius; final double radius;
/// The colors the gradient should obtain at each of the stops
///
/// Note: This list must have the same length as [stops].
final List<Color> colors; final List<Color> colors;
final List<double> colorStops;
/// A list of values from 0.0 to 1.0 that denote concentric rings
///
/// The rings are centered at [center] and have a radius equal to the value of
/// the stop times [radius].
///
/// Note: This list must have the same length as [colors].
final List<double> stops;
/// How this gradient should tile the plane
final sky.TileMode tileMode; final sky.TileMode tileMode;
sky.Shader createShader() { sky.Shader createShader() {
return new sky.Gradient.radial(this.center, this.radius, this.colors, return new sky.Gradient.radial(center, radius, colors, stops, tileMode);
this.colorStops, this.tileMode);
} }
String toString() { String toString() {
return 'RadialGradient($center, $radius, $colors, $colorStops, $tileMode)'; return 'RadialGradient($center, $radius, $colors, $stops, $tileMode)';
} }
} }
enum ImageFit { fill, contain, cover, none, scaleDown } /// How an image should be inscribed into a box
enum ImageFit {
/// Fill the box by distorting the image's aspect ratio
fill,
/// As large as possible while still containing the image entirely within the box
contain,
/// As small as possible while still covering the entire box
cover,
/// Center the image within the box and discard any portions of the image that
/// lie outside the box
none,
/// Center the image within the box and, if necessary, scale the image down to
/// ensure that the image fits within the box
scaleDown
}
/// How to paint any portions of a box not covered by an image
enum ImageRepeat {
/// Repeat the image in both the x and y directions until the box is filled
repeat,
/// Repeat the image in the x direction until the box is filled horizontally
repeatX,
enum ImageRepeat { repeat, repeatX, repeatY, noRepeat } /// Repeat the image in the y direction until the box is filled vertically
repeatY,
/// Leave uncovered poritions of the box transparent
noRepeat
}
/// Paint an image into the given rectangle in the canvas
void paintImage({ void paintImage({
sky.Canvas canvas, sky.Canvas canvas,
Rect rect, Rect rect,
...@@ -289,9 +409,15 @@ void paintImage({ ...@@ -289,9 +409,15 @@ void paintImage({
typedef void BackgroundImageChangeListener(); typedef void BackgroundImageChangeListener();
/// A background image for a box
class BackgroundImage { class BackgroundImage {
/// How the background image should be inscribed into the box
final ImageFit fit; final ImageFit fit;
/// How to paint any portions of the box not covered by the background image
final ImageRepeat repeat; final ImageRepeat repeat;
/// A color filter to apply to the background image before painting it
final sky.ColorFilter colorFilter; final sky.ColorFilter colorFilter;
BackgroundImage({ BackgroundImage({
...@@ -302,6 +428,7 @@ class BackgroundImage { ...@@ -302,6 +428,7 @@ class BackgroundImage {
}) : _imageResource = image; }) : _imageResource = image;
sky.Image _image; sky.Image _image;
/// The image to be painted into the background
sky.Image get image => _image; sky.Image get image => _image;
ImageResource _imageResource; ImageResource _imageResource;
...@@ -309,6 +436,7 @@ class BackgroundImage { ...@@ -309,6 +436,7 @@ class BackgroundImage {
final List<BackgroundImageChangeListener> _listeners = final List<BackgroundImageChangeListener> _listeners =
new List<BackgroundImageChangeListener>(); new List<BackgroundImageChangeListener>();
/// Call listener when the background images changes (e.g., arrives from the network)
void addChangeListener(BackgroundImageChangeListener listener) { void addChangeListener(BackgroundImageChangeListener listener) {
// We add the listener to the _imageResource first so that the first change // We add the listener to the _imageResource first so that the first change
// listener doesn't get callback synchronously if the image resource is // listener doesn't get callback synchronously if the image resource is
...@@ -318,6 +446,7 @@ class BackgroundImage { ...@@ -318,6 +446,7 @@ class BackgroundImage {
_listeners.add(listener); _listeners.add(listener);
} }
/// No longer call listener when the background image changes
void removeChangeListener(BackgroundImageChangeListener listener) { void removeChangeListener(BackgroundImageChangeListener listener) {
_listeners.remove(listener); _listeners.remove(listener);
// We need to remove ourselves as listeners from the _imageResource so that // We need to remove ourselves as listeners from the _imageResource so that
...@@ -340,9 +469,17 @@ class BackgroundImage { ...@@ -340,9 +469,17 @@ class BackgroundImage {
String toString() => 'BackgroundImage($fit, $repeat)'; String toString() => 'BackgroundImage($fit, $repeat)';
} }
enum Shape { rectangle, circle } // TODO(abarth): Rename to BoxShape?
/// A 2D geometrical shape
enum Shape {
/// An axis-aligned, 2D rectangle
rectangle,
// This must be immutable, because we won't notice when it changes /// A 2D locus of points equidistant from a single point
circle
}
/// An immutable description of how to paint a box
class BoxDecoration { class BoxDecoration {
const BoxDecoration({ const BoxDecoration({
this.backgroundColor, // null = don't draw background color this.backgroundColor, // null = don't draw background color
...@@ -354,14 +491,33 @@ class BoxDecoration { ...@@ -354,14 +491,33 @@ class BoxDecoration {
this.shape: Shape.rectangle this.shape: Shape.rectangle
}); });
/// The color to fill in the background of the box
///
/// The color is filled into the shape of the box (e.g., either a rectangle,
/// potentially with a border radius, or a circle).
final Color backgroundColor; final Color backgroundColor;
/// An image to paint above the background color
final BackgroundImage backgroundImage; final BackgroundImage backgroundImage;
final double borderRadius;
/// A border to draw above the background
final Border border; final Border border;
/// If non-null, the corners of this box are rounded by this radius
///
/// Applies only to boxes with rectangular shapes.
final double borderRadius;
/// A list of shadows cast by this box behind the background
final List<BoxShadow> boxShadow; final List<BoxShadow> boxShadow;
/// A graident to use when filling the background
final Gradient gradient; final Gradient gradient;
/// The shape to fill the background color into and to cast as a shadow
final Shape shape; final Shape shape;
/// Returns a new box decoration that is scalled by the given factor
BoxDecoration scale(double factor) { BoxDecoration scale(double factor) {
// TODO(abarth): Scale ALL the things. // TODO(abarth): Scale ALL the things.
return new BoxDecoration( return new BoxDecoration(
...@@ -369,12 +525,34 @@ class BoxDecoration { ...@@ -369,12 +525,34 @@ class BoxDecoration {
backgroundImage: backgroundImage, backgroundImage: backgroundImage,
border: border, border: border,
borderRadius: lerpNum(null, borderRadius, factor), borderRadius: lerpNum(null, borderRadius, factor),
boxShadow: lerpListBoxShadow(null, boxShadow, factor), boxShadow: BoxShadow.lerpList(null, boxShadow, factor),
gradient: gradient, gradient: gradient,
shape: shape shape: shape
); );
} }
/// Linearly interpolate between two box decorations
///
/// Interpolates each parameter of the box decoration separately.
static BoxDecoration lerp(BoxDecoration a, BoxDecoration b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
// TODO(abarth): lerp ALL the fields.
return new BoxDecoration(
backgroundColor: lerpColor(a.backgroundColor, b.backgroundColor, t),
backgroundImage: b.backgroundImage,
border: b.border,
borderRadius: lerpNum(a.borderRadius, b.borderRadius, t),
boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
gradient: b.gradient,
shape: b.shape
);
}
String toString([String prefix = '']) { String toString([String prefix = '']) {
List<String> result = []; List<String> result = [];
if (backgroundColor != null) if (backgroundColor != null)
...@@ -397,31 +575,14 @@ class BoxDecoration { ...@@ -397,31 +575,14 @@ class BoxDecoration {
} }
} }
BoxDecoration lerpBoxDecoration(BoxDecoration a, BoxDecoration b, double t) { /// An object that paints a [BoxDecoration] into a canvas
if (a == null && b == null)
return null;
if (a == null)
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
// TODO(abarth): lerp ALL the fields.
return new BoxDecoration(
backgroundColor: lerpColor(a.backgroundColor, b.backgroundColor, t),
backgroundImage: b.backgroundImage,
border: b.border,
borderRadius: lerpNum(a.borderRadius, b.borderRadius, t),
boxShadow: lerpListBoxShadow(a.boxShadow, b.boxShadow, t),
gradient: b.gradient,
shape: b.shape
);
}
class BoxPainter { class BoxPainter {
BoxPainter(BoxDecoration decoration) : _decoration = decoration { BoxPainter(BoxDecoration decoration) : _decoration = decoration {
assert(decoration != null); assert(decoration != null);
} }
BoxDecoration _decoration; BoxDecoration _decoration;
/// The box decoration to paint
BoxDecoration get decoration => _decoration; BoxDecoration get decoration => _decoration;
void set decoration (BoxDecoration value) { void set decoration (BoxDecoration value) {
assert(value != null); assert(value != null);
...@@ -611,6 +772,7 @@ class BoxPainter { ...@@ -611,6 +772,7 @@ class BoxPainter {
canvas.drawCircle(center, radius, paint); canvas.drawCircle(center, radius, paint);
} }
/// Paint the box decoration into the given location on the given canvas
void paint(sky.Canvas canvas, Rect rect) { void paint(sky.Canvas canvas, Rect rect) {
_paintBackgroundColor(canvas, rect); _paintBackgroundColor(canvas, rect);
_paintBackgroundImage(canvas, rect); _paintBackgroundImage(canvas, rect);
......
...@@ -20,6 +20,9 @@ int _roundOpacity(double opacity) { ...@@ -20,6 +20,9 @@ int _roundOpacity(double opacity) {
return (255 * opacity).round(); return (255 * opacity).round();
} }
/// A material design radial ink reaction
///
/// See [https://www.google.com/design/spec/animation/responsive-interaction.html#responsive-interaction-radial-action]
class RadialReaction { class RadialReaction {
RadialReaction({ RadialReaction({
this.center, this.center,
...@@ -41,7 +44,10 @@ class RadialReaction { ...@@ -41,7 +44,10 @@ class RadialReaction {
..duration =_kHideDuration; ..duration =_kHideDuration;
} }
/// The center of the circle in which the reaction occurs
final Point center; final Point center;
/// The radius of the circle in which the reaction occurs
final double radius; final double radius;
AnimationPerformance _showPerformance; AnimationPerformance _showPerformance;
...@@ -54,15 +60,22 @@ class RadialReaction { ...@@ -54,15 +60,22 @@ class RadialReaction {
AnimationPerformance _hidePerformance; AnimationPerformance _hidePerformance;
AnimatedValue<double> _fade; AnimatedValue<double> _fade;
void show() { /// Show the reaction
_showComplete = _showPerformance.forward(); ///
/// Returns a future that resolves when the reaction is completely revealed.
Future show() {
return _showComplete = _showPerformance.forward();
} }
/// Hide the reaction
///
/// Returns a future that resolves when the reaction is completely hidden.
Future hide() async { Future hide() async {
await _showComplete; await _showComplete;
await _hidePerformance.forward(); await _hidePerformance.forward();
} }
/// Call listener whenever the visual appearance of the reaction changes
void addListener(Function listener) { void addListener(Function listener) {
_showPerformance.addListener(listener); _showPerformance.addListener(listener);
_hidePerformance.addListener(listener); _hidePerformance.addListener(listener);
...@@ -71,6 +84,7 @@ class RadialReaction { ...@@ -71,6 +84,7 @@ class RadialReaction {
final Paint _outerPaint = new Paint(); final Paint _outerPaint = new Paint();
final Paint _innerPaint = new Paint(); final Paint _innerPaint = new Paint();
/// Paint the reaction onto the given canvas at the given offset
void paint(sky.Canvas canvas, Offset offset) { void paint(sky.Canvas canvas, Offset offset) {
_outerPaint.color = _kOuterColor.withAlpha(_roundOpacity(_outerOpacity.value * _fade.value)); _outerPaint.color = _kOuterColor.withAlpha(_roundOpacity(_outerOpacity.value * _fade.value));
canvas.drawCircle(center + offset, radius, _outerPaint); canvas.drawCircle(center + offset, radius, _outerPaint);
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
/// Helper class to build a Paint DrawLooper that adds shadows to the Paint's /// A helper class to build a [sky.DrawLooper] for drawing shadows
/// operation.
class ShadowDrawLooperBuilder { class ShadowDrawLooperBuilder {
var builder_ = new sky.LayerDrawLooperBuilder(); var builder_ = new sky.LayerDrawLooperBuilder();
/// Add a shadow with the given parameters
void addShadow(sky.Offset offset, sky.Color color, double blur) { void addShadow(sky.Offset offset, sky.Color color, double blur) {
builder_.addLayerOnTop( builder_.addLayerOnTop(
new sky.DrawLooperLayerInfo() new sky.DrawLooperLayerInfo()
...@@ -20,6 +20,7 @@ class ShadowDrawLooperBuilder { ...@@ -20,6 +20,7 @@ class ShadowDrawLooperBuilder {
..setMaskFilter(new sky.MaskFilter.blur(sky.BlurStyle.normal, blur))); ..setMaskFilter(new sky.MaskFilter.blur(sky.BlurStyle.normal, blur)));
} }
/// Returns the draw looper built for the added shadows
sky.DrawLooper build() { sky.DrawLooper build() {
builder_.addLayerOnTop(new sky.DrawLooperLayerInfo(), new sky.Paint()); builder_.addLayerOnTop(new sky.DrawLooperLayerInfo(), new sky.Paint());
return builder_.build(); return builder_.build();
......
...@@ -8,8 +8,9 @@ import 'package:sky/painting/text_style.dart'; ...@@ -8,8 +8,9 @@ import 'package:sky/painting/text_style.dart';
export 'package:sky/painting/text_style.dart'; export 'package:sky/painting/text_style.dart';
// This must be immutable, because we won't notice when it changes /// An immutable span of text
abstract class TextSpan { abstract class TextSpan {
// This class must be immutable, because we won't notice when it changes
sky.Node _toDOM(sky.Document owner); sky.Node _toDOM(sky.Document owner);
String toString([String prefix = '']); String toString([String prefix = '']);
...@@ -17,11 +18,13 @@ abstract class TextSpan { ...@@ -17,11 +18,13 @@ abstract class TextSpan {
} }
} }
/// An immutable span of unstyled text
class PlainTextSpan extends TextSpan { class PlainTextSpan extends TextSpan {
PlainTextSpan(this.text) { PlainTextSpan(this.text) {
assert(text != null); assert(text != null);
} }
/// The text contained in the span
final String text; final String text;
sky.Node _toDOM(sky.Document owner) { sky.Node _toDOM(sky.Document owner) {
...@@ -34,13 +37,17 @@ class PlainTextSpan extends TextSpan { ...@@ -34,13 +37,17 @@ class PlainTextSpan extends TextSpan {
String toString([String prefix = '']) => '${prefix}${runtimeType}: "${text}"'; String toString([String prefix = '']) => '${prefix}${runtimeType}: "${text}"';
} }
/// An immutable text span that applies a style to a list of children
class StyledTextSpan extends TextSpan { class StyledTextSpan extends TextSpan {
StyledTextSpan(this.style, this.children) { StyledTextSpan(this.style, this.children) {
assert(style != null); assert(style != null);
assert(children != null); assert(children != null);
} }
/// The style to apply to the children
final TextStyle style; final TextStyle style;
/// The children to which the style is applied
final List<TextSpan> children; final List<TextSpan> children;
sky.Node _toDOM(sky.Document owner) { sky.Node _toDOM(sky.Document owner) {
...@@ -90,6 +97,7 @@ class StyledTextSpan extends TextSpan { ...@@ -90,6 +97,7 @@ class StyledTextSpan extends TextSpan {
} }
} }
/// An object that paints a [TextSpan] into a canvas
class TextPainter { class TextPainter {
TextPainter(TextSpan text) { TextPainter(TextSpan text) {
_layoutRoot.rootElement = _document.createElement('p'); _layoutRoot.rootElement = _document.createElement('p');
...@@ -102,6 +110,7 @@ class TextPainter { ...@@ -102,6 +110,7 @@ class TextPainter {
bool _needsLayout = true; bool _needsLayout = true;
TextSpan _text; TextSpan _text;
/// The (potentially styled) text to paint
TextSpan get text => _text; TextSpan get text => _text;
void set text(TextSpan value) { void set text(TextSpan value) {
if (_text == value) if (_text == value)
...@@ -113,6 +122,7 @@ class TextPainter { ...@@ -113,6 +122,7 @@ class TextPainter {
_needsLayout = true; _needsLayout = true;
} }
/// The minimum width at which to layout the text
double get minWidth => _layoutRoot.minWidth; double get minWidth => _layoutRoot.minWidth;
void set minWidth(value) { void set minWidth(value) {
if (_layoutRoot.minWidth == value) if (_layoutRoot.minWidth == value)
...@@ -121,6 +131,7 @@ class TextPainter { ...@@ -121,6 +131,7 @@ class TextPainter {
_needsLayout = true; _needsLayout = true;
} }
/// The maximum width at which to layout the text
double get maxWidth => _layoutRoot.maxWidth; double get maxWidth => _layoutRoot.maxWidth;
void set maxWidth(value) { void set maxWidth(value) {
if (_layoutRoot.maxWidth == value) if (_layoutRoot.maxWidth == value)
...@@ -129,6 +140,7 @@ class TextPainter { ...@@ -129,6 +140,7 @@ class TextPainter {
_needsLayout = true; _needsLayout = true;
} }
/// The minimum height at which to layout the text
double get minHeight => _layoutRoot.minHeight; double get minHeight => _layoutRoot.minHeight;
void set minHeight(value) { void set minHeight(value) {
if (_layoutRoot.minHeight == value) if (_layoutRoot.minHeight == value)
...@@ -137,6 +149,7 @@ class TextPainter { ...@@ -137,6 +149,7 @@ class TextPainter {
_needsLayout = true; _needsLayout = true;
} }
/// The maximum height at which to layout the text
double get maxHeight => _layoutRoot.maxHeight; double get maxHeight => _layoutRoot.maxHeight;
void set maxHeight(value) { void set maxHeight(value) {
if (_layoutRoot.maxHeight == value) if (_layoutRoot.maxHeight == value)
...@@ -144,21 +157,25 @@ class TextPainter { ...@@ -144,21 +157,25 @@ class TextPainter {
_layoutRoot.maxHeight = value; _layoutRoot.maxHeight = value;
} }
/// The width at which decreasing the width of the text would prevent it from painting itself completely within its bounds
double get minContentWidth { double get minContentWidth {
assert(!_needsLayout); assert(!_needsLayout);
return _layoutRoot.rootElement.minContentWidth; return _layoutRoot.rootElement.minContentWidth;
} }
/// The width at which increasing the width of the text no longer decreases the height
double get maxContentWidth { double get maxContentWidth {
assert(!_needsLayout); assert(!_needsLayout);
return _layoutRoot.rootElement.maxContentWidth; return _layoutRoot.rootElement.maxContentWidth;
} }
/// The height required to paint the text completely within its bounds
double get height { double get height {
assert(!_needsLayout); assert(!_needsLayout);
return _layoutRoot.rootElement.height; return _layoutRoot.rootElement.height;
} }
/// The distance from the top of the text to the first baseline of the given type
double computeDistanceToActualBaseline(TextBaseline baseline) { double computeDistanceToActualBaseline(TextBaseline baseline) {
assert(!_needsLayout); assert(!_needsLayout);
sky.Element root = _layoutRoot.rootElement; sky.Element root = _layoutRoot.rootElement;
...@@ -168,6 +185,7 @@ class TextPainter { ...@@ -168,6 +185,7 @@ class TextPainter {
} }
} }
/// Compute the visual position of the glyphs for painting the text
void layout() { void layout() {
if (!_needsLayout) if (!_needsLayout)
return; return;
...@@ -175,6 +193,7 @@ class TextPainter { ...@@ -175,6 +193,7 @@ class TextPainter {
_needsLayout = false; _needsLayout = false;
} }
/// Paint the text onto the given canvas at the given offset
void paint(sky.Canvas canvas, sky.Offset offset) { void paint(sky.Canvas canvas, sky.Offset offset) {
assert(!_needsLayout && "Please call layout() before paint() to position the text before painting it." is String); assert(!_needsLayout && "Please call layout() before paint() to position the text before painting it." is String);
// TODO(ianh): Make LayoutRoot support a paint offset so we don't // TODO(ianh): Make LayoutRoot support a paint offset so we don't
......
...@@ -4,23 +4,118 @@ ...@@ -4,23 +4,118 @@
import 'dart:sky'; import 'dart:sky';
enum FontWeight { w100, w200, w300, w400, w500, w600, w700, w800, w900 } /// The thickness of the glyphs used to draw the text
enum FontWeight {
/// Thin, the least thick
w100,
/// Extra-light
w200,
/// Light
w300,
/// Normal / regular / plain
w400,
/// Medium
w500,
/// Semi-bold
w600,
/// Bold
w700,
/// Extra-bold
w800,
/// Black, the most thick
w900
}
/// A normal font weight
const normal = FontWeight.w400; const normal = FontWeight.w400;
/// A bold font weight
const bold = FontWeight.w700; const bold = FontWeight.w700;
enum FontStyle { normal, italic, oblique } /// Whether to slant the glyphs in the font
enum FontStyle {
/// Use the upright glyphs
normal,
/// Use glyphs designed for slanting
italic,
/// Use the upright glyphs but slant them during painting
oblique // TODO(abarth): Remove. We don't really support this value.
}
/// Whether to align text horizontally
enum TextAlign {
/// Align the text on the left edge of the container
left,
/// Align the text on the right edge of the container
right,
/// Align the text in the center of the container
center
}
/// A horizontal line used for aligning text
enum TextBaseline {
// The horizontal line used to align the bottom of glyphs for alphabetic characters
alphabetic,
// The horizontal line used to align ideographic characters
ideographic
}
enum TextAlign { left, right, center } /// A linear decoration to draw near the text
enum TextDecoration {
/// Do not draw a decoration
none,
enum TextBaseline { alphabetic, ideographic } /// Draw a line underneath each line of text
underline,
enum TextDecoration { none, underline, overline, lineThrough } /// Draw a line above each line of text
overline,
/// Draw a line through each line of text
lineThrough
}
/// Draw a line underneath each line of text
const underline = const <TextDecoration>[TextDecoration.underline]; const underline = const <TextDecoration>[TextDecoration.underline];
/// Draw a line above each line of text
const overline = const <TextDecoration>[TextDecoration.overline]; const overline = const <TextDecoration>[TextDecoration.overline];
/// Draw a line through each line of text
const lineThrough = const <TextDecoration>[TextDecoration.lineThrough]; const lineThrough = const <TextDecoration>[TextDecoration.lineThrough];
enum TextDecorationStyle { solid, double, dotted, dashed, wavy } /// The style in which to draw a text decoration
enum TextDecorationStyle {
/// Draw a solid line
solid,
/// Draw two lines
double,
/// Draw a dotted line
dotted,
/// Draw a dashed line
dashed,
/// Draw a sinusoidal line
wavy
}
/// An immutable style in which paint text
class TextStyle { class TextStyle {
const TextStyle({ const TextStyle({
this.color, this.color,
...@@ -36,18 +131,42 @@ class TextStyle { ...@@ -36,18 +131,42 @@ class TextStyle {
this.decorationStyle this.decorationStyle
}); });
/// The color to use when painting the text
final Color color; final Color color;
/// The name of the font to use when painting the text
final String fontFamily; final String fontFamily;
final double fontSize; // in pixels
/// The size of gyphs (in logical pixels) to use when painting the text
final double fontSize;
/// The font weight to use when painting the text
final FontWeight fontWeight; final FontWeight fontWeight;
/// The font style to use when painting the text
final FontStyle fontStyle; final FontStyle fontStyle;
/// How the text should be aligned (applies only to the outermost
/// StyledTextSpan, which establishes the container for the text)
final TextAlign textAlign; final TextAlign textAlign;
/// The baseline to use for aligning the text
final TextBaseline textBaseline; final TextBaseline textBaseline;
final double height; // multiple of fontSize
/// The distance between the text baselines, as a multiple of the font size
final double height;
/// A list of decorations to paint near the text
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
/// The color in which to paint the text decorations
final Color decorationColor; final Color decorationColor;
/// The style in which to paint the text decorations
final TextDecorationStyle decorationStyle; final TextDecorationStyle decorationStyle;
/// Returns a new text style that matches this text style but with the given
/// values replaced
TextStyle copyWith({ TextStyle copyWith({
Color color, Color color,
String fontFamily, String fontFamily,
...@@ -76,6 +195,8 @@ class TextStyle { ...@@ -76,6 +195,8 @@ class TextStyle {
); );
} }
/// Returns a new text style that matches this text style but with some values
/// replaced by the non-null parameters of the given text style
TextStyle merge(TextStyle other) { TextStyle merge(TextStyle other) {
return copyWith( return copyWith(
color: other.color, color: other.color,
...@@ -124,6 +245,10 @@ class TextStyle { ...@@ -124,6 +245,10 @@ class TextStyle {
return toCSS[decorationStyle]; return toCSS[decorationStyle];
} }
/// Program this text style into the engine
///
/// Note: This function will likely be removed when we refactor the interface
/// between the framework and the engine
void applyToCSSStyle(CSSStyleDeclaration cssStyle) { void applyToCSSStyle(CSSStyleDeclaration cssStyle) {
if (color != null) { if (color != null) {
cssStyle['color'] = _colorToCSSString(color); cssStyle['color'] = _colorToCSSString(color);
...@@ -163,6 +288,10 @@ class TextStyle { ...@@ -163,6 +288,10 @@ class TextStyle {
} }
} }
/// Program the container aspects of this text style into the engine
///
/// Note: This function will likely be removed when we refactor the interface
/// between the framework and the engine
void applyToContainerCSSStyle(CSSStyleDeclaration cssStyle) { void applyToContainerCSSStyle(CSSStyleDeclaration cssStyle) {
if (textAlign != null) { if (textAlign != null) {
cssStyle['text-align'] = const { cssStyle['text-align'] = const {
......
...@@ -131,7 +131,8 @@ import 'package:sky/widgets.dart'; ...@@ -131,7 +131,8 @@ import 'package:sky/widgets.dart';
final BoxDecoration _decoration = new BoxDecoration( final BoxDecoration _decoration = new BoxDecoration(
borderRadius: 5.0, borderRadius: 5.0,
gradient: new LinearGradient( gradient: new LinearGradient(
endPoints: [ Point.origin, const Point(0.0, 36.0) ], start: Point.origin,
end: const Point(0.0, 36.0),
colors: [ const Color(0xFFEEEEEE), const Color(0xFFCCCCCC) ] colors: [ const Color(0xFFEEEEEE), const Color(0xFFCCCCCC) ]
) )
); );
......
...@@ -25,7 +25,7 @@ class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> { ...@@ -25,7 +25,7 @@ class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> {
AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear }) AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear })
: super(begin, end: end, curve: curve); : super(begin, end: end, curve: curve);
BoxDecoration lerp(double t) => lerpBoxDecoration(begin, end, t); BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t);
} }
class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> { class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
......
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