From 61b71a6f321c0106008a99fe6bc76d136bf6e4ad Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Tue, 16 Jun 2015 16:48:08 -0700 Subject: [PATCH] Styling for text fragments This is a completion of Eric's WIP patch: https://codereview.chromium.org/1179663005/ Low level support for creating a paragraph that contains runs of styled text. The styles may be nested. The Paragraph and RenderParagraph classes have been replaced by Inline and RenderInline. Styled text is defined with a tree of InlineText and InlineStyle objects. R=abarth@chromium.org Review URL: https://codereview.chromium.org/1177833012. --- examples/rendering/interactive_flex.dart | 8 +- examples/rendering/justify_content.dart | 2 +- examples/rendering/render_paragraph.dart | 5 +- examples/rendering/touch_demo.dart | 11 +- sdk/lib/editing2/editable_text.dart | 4 +- sdk/lib/rendering/paragraph.dart | 139 ++++++++++++++--------- sdk/lib/widgets/basic.dart | 21 ++-- tests/framework/stocks-expected.txt | 2 +- 8 files changed, 113 insertions(+), 79 deletions(-) diff --git a/examples/rendering/interactive_flex.dart b/examples/rendering/interactive_flex.dart index 4aaed292c..bdcf71580 100644 --- a/examples/rendering/interactive_flex.dart +++ b/examples/rendering/interactive_flex.dart @@ -75,8 +75,12 @@ porchetta bacon kevin meatball meatloaf pig beef ribs chicken. Brisket ribeye andouille leberkas capicola meatloaf. Chicken pig ball tip pork picanha bresaola alcatra. Pork pork belly alcatra, flank chuck drumstick biltong doner jowl. Pancetta meatball tongue tenderloin rump tail jowl boudin."""; - RenderParagraph paragraph = new RenderParagraph(text: meatyString, color: const Color(0xFF009900)); - padding = new RenderPadding(padding: const EdgeDims.all(10.0), child: paragraph); + var text = new InlineStyle( + new TextStyle(color: const Color(0xFF009900)), + [new InlineText(meatyString)]); + padding = new RenderPadding( + padding: const EdgeDims.all(10.0), + child: new RenderParagraph(text)); column.add(padding); // Bottom cell diff --git a/examples/rendering/justify_content.dart b/examples/rendering/justify_content.dart index f267a2f43..ef7cf3fd5 100644 --- a/examples/rendering/justify_content.dart +++ b/examples/rendering/justify_content.dart @@ -20,7 +20,7 @@ void main() { var table = new RenderFlex(direction: FlexDirection.vertical); void addRow(FlexJustifyContent justify) { - RenderParagraph paragraph = new RenderParagraph(text: "${justify}"); + RenderParagraph paragraph = new RenderParagraph(new InlineText("${justify}")); table.add(new RenderPadding(child: paragraph, padding: new EdgeDims.only(top: 20.0))); var row = new RenderFlex(direction: FlexDirection.horizontal); row.add(new RenderSolidColorBox(const Color(0xFFFFCCCC), desiredSize: new Size(80.0, 60.0))); diff --git a/examples/rendering/render_paragraph.dart b/examples/rendering/render_paragraph.dart index 7ed158ff4..918cbcd33 100644 --- a/examples/rendering/render_paragraph.dart +++ b/examples/rendering/render_paragraph.dart @@ -31,9 +31,12 @@ andouille leberkas capicola meatloaf. Chicken pig ball tip pork picanha bresaola alcatra. Pork pork belly alcatra, flank chuck drumstick biltong doner jowl. Pancetta meatball tongue tenderloin rump tail jowl boudin."""; + var text = new InlineStyle( + new TextStyle(color: const Color(0xFF009900)), + [new InlineText(meatyString)]); child = new RenderDecoratedBox( decoration: new BoxDecoration(backgroundColor: const Color(0xFFFFFFFF)), - child: new RenderParagraph(text: meatyString, color: const Color(0xFF009900)) + child: new RenderParagraph(text) ); flexRoot.add(child); child.parentData.flex = 1; diff --git a/examples/rendering/touch_demo.dart b/examples/rendering/touch_demo.dart index 802ef5933..5f701fe32 100644 --- a/examples/rendering/touch_demo.dart +++ b/examples/rendering/touch_demo.dart @@ -80,13 +80,14 @@ class RenderTouchDemo extends RenderBox { AppView app; void main() { - var para = new RenderParagraph(text: "Touch me!"); + var paragraph = new RenderParagraph(new InlineText("Touch me!")); var stack = new RenderStack(children: [ new RenderTouchDemo(), - para, + paragraph, ]); - // Make the paragraph not fill the whole screen so it doesn't eat events. - para.parentData..top = 40.0 - ..left = 20.0; + // Prevent the RenderParagraph from filling the whole screen so + // that it doesn't eat events. + paragraph.parentData..top = 40.0 + ..left = 20.0; app = new AppView(root: stack); } diff --git a/sdk/lib/editing2/editable_text.dart b/sdk/lib/editing2/editable_text.dart index e59011f35..3f5bea548 100644 --- a/sdk/lib/editing2/editable_text.dart +++ b/sdk/lib/editing2/editable_text.dart @@ -93,8 +93,6 @@ class EditableText extends Component { // // style: _cursorStyle // )); - return new Paragraph( - text: hack - ); + return new Text(hack); } } diff --git a/sdk/lib/rendering/paragraph.dart b/sdk/lib/rendering/paragraph.dart index 8ef6d4eeb..cc04d0508 100644 --- a/sdk/lib/rendering/paragraph.dart +++ b/sdk/lib/rendering/paragraph.dart @@ -7,11 +7,6 @@ import 'dart:sky' as sky; import 'box.dart'; import 'object.dart'; -class RenderInline extends RenderObject { - RenderInline(this.data); - String data; -} - enum FontWeight { light, // 300 regular, // 400 @@ -69,6 +64,29 @@ class TextStyle { return value; } + void _applyToCSSStyle(sky.CSSStyleDeclaration cssStyle) { + if (color != null) { + cssStyle['color'] = 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})'; + } + if (fontSize != null) { + cssStyle['font-size'] = "${fontSize}px"; + } + if (fontWeight != null) { + cssStyle['font-weight'] = const { + FontWeight.light: '300', + FontWeight.regular: '400', + FontWeight.medium: '500', + }[fontWeight]; + } + if (textAlign != null) { + cssStyle['text-align'] = const { + TextAlign.left: 'left', + TextAlign.right: 'right', + TextAlign.center: 'center', + }[textAlign]; + } + } + String toString([String prefix = '']) { List result = []; if (color != null) @@ -76,15 +94,63 @@ class TextStyle { if (fontSize != null) result.add('${prefix}fontSize: $fontSize'); if (fontWeight != null) - result.add('${prefix}fontWeight: fontWeight'); + result.add('${prefix}fontWeight: $fontWeight'); if (textAlign != null) - result.add('${prefix}textAlign: textAlign'); + result.add('${prefix}textAlign: $textAlign'); if (result.isEmpty) return '${prefix}'; return result.join('\n'); } } +class InlineBase { + sky.Node _toDOM(sky.Document owner); + String toString([String prefix = '']); +} + +class InlineText extends InlineBase { + InlineText(this.text) { + assert(text != null); + } + + final String text; + + sky.Node _toDOM(sky.Document owner) { + return owner.createText(text); + } + + String toString([String prefix = '']) => '${prefix}InlineText: "${text}"'; +} + +class InlineStyle extends InlineBase { + InlineStyle(this.style, this.children) { + assert(style != null && children != null); + } + + final TextStyle style; + final List children; + + sky.Node _toDOM(sky.Document owner) { + sky.Element parent = owner.createElement('t'); + style._applyToCSSStyle(parent.style); + for (InlineBase child in children) { + parent.appendChild(child._toDOM(owner)); + } + return parent; + } + + String toString([String prefix = '']) { + List result = []; + result.add('${prefix}InlineStyle:'); + var indent = '${prefix} '; + result.add('${style.toString(indent)}'); + for (InlineBase child in children) { + result.add(child.toString(indent)); + } + return result.join('\n'); + } +} + // Unfortunately, using full precision floating point here causes bad layouts // because floating point math isn't associative. If we add and subtract // padding, for example, we'll get different values when we estimate sizes and @@ -98,36 +164,24 @@ double _applyFloatingPointHack(double layoutValue) { class RenderParagraph extends RenderBox { - RenderParagraph({ - String text, - Color color, - TextStyle style - }) : _style = style { + RenderParagraph(InlineBase inlineValue) { _layoutRoot.rootElement = _document.createElement('p'); - this.text = text; + inline = inlineValue; } final sky.Document _document = new sky.Document(); final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot(); - String get text => (_layoutRoot.rootElement.firstChild as sky.Text).data; - void set text (String value) { - _layoutRoot.rootElement.setChild(_document.createText(value)); - markNeedsLayout(); - } + InlineBase _inline; + BoxConstraints _constraintsForCurrentLayout; - TextStyle _style; - TextStyle get style => _style; - void set style (TextStyle value) { - if (_style != value) { - // TODO(hansmuller): decide if a new layout or paint is needed - markNeedsLayout(); - _style = value; - } + String get inline => _inline; + void set inline (InlineBase value) { + _inline = value; + _layoutRoot.rootElement.setChild(_inline._toDOM(_document)); + markNeedsLayout(); } - BoxConstraints _constraintsForCurrentLayout; - sky.Element _layout(BoxConstraints constraints) { _layoutRoot.maxWidth = constraints.maxWidth; _layoutRoot.minWidth = constraints.minWidth; @@ -182,31 +236,6 @@ class RenderParagraph extends RenderBox { if (_constraintsForCurrentLayout != constraints && constraints != null) _layout(constraints); - if (style != null) { - var cssStyle = _layoutRoot.rootElement.style; - if (style.color != null) { - Color c = style.color; - cssStyle['color'] = - 'rgba(${c.red}, ${c.green}, ${c.blue}, ${c.alpha / 255.0})'; - } - if (style.fontSize != null) { - cssStyle['font-size'] = "${style.fontSize}px"; - } - if (style.fontWeight != null) { - cssStyle['font-weight'] = const { - FontWeight.light: '300', - FontWeight.regular: '400', - FontWeight.medium: '500', - }[style.fontWeight]; - } - if (style.textAlign != null) { - cssStyle['text-align'] = const { - TextAlign.left: 'left', - TextAlign.right: 'right', - TextAlign.center: 'center', - }[style.textAlign]; - } - } _layoutRoot.paint(canvas); } @@ -214,9 +243,7 @@ class RenderParagraph extends RenderBox { String debugDescribeSettings(String prefix) { String result = '${super.debugDescribeSettings(prefix)}'; - if (style != null) - result += '${prefix}style:\n' + style.toString('$prefix ') + '\n'; - result += '${prefix}text: ${text}\n'; + result += '${prefix}inline: ${inline}\n'; return result; } } diff --git a/sdk/lib/widgets/basic.dart b/sdk/lib/widgets/basic.dart index 68b4f7363..0681eb17e 100644 --- a/sdk/lib/widgets/basic.dart +++ b/sdk/lib/widgets/basic.dart @@ -345,25 +345,22 @@ class Flexible extends ParentDataNode { : super(child, new FlexBoxParentData()..flex = flex, key: key); } -class Paragraph extends RenderObjectWrapper { - - Paragraph({ String key, this.text, this.style }) : super(key: key); +class Inline extends RenderObjectWrapper { + Inline({ Object key, this.text }) : super(key: key); RenderParagraph get root => super.root; - RenderParagraph createNode() => new RenderParagraph(text: text, style: style); + RenderParagraph createNode() => new RenderParagraph(text); - final String text; - final TextStyle style; + final InlineBase text; void syncRenderObject(Widget old) { super.syncRenderObject(old); - root.text = text; - root.style = style; + root.inline = text; } void insert(RenderObjectWrapper child, dynamic slot) { assert(false); - // Paragraph does not support having children currently + // Inline does not support having children currently } } @@ -373,7 +370,11 @@ class Text extends Component { final String data; final TextStyle style; bool get interchangeable => true; - Widget build() => new Paragraph(text: data, style: style); + Widget build() { + InlineBase text = new InlineText(data); + if (style != null) text = new InlineStyle(style, [text]); + return new Inline(text: text); + } } class Image extends RenderObjectWrapper { diff --git a/tests/framework/stocks-expected.txt b/tests/framework/stocks-expected.txt index 61678fd53..814f4570f 100644 --- a/tests/framework/stocks-expected.txt +++ b/tests/framework/stocks-expected.txt @@ -31,7 +31,7 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | TestDisplayList() constructor: 800.0 x 600.0 2 | | | | | | | paintChild RenderImage at Point(8.0, 8.0) 2 | | | | | | | | TestDisplayList() constructor: 800.0 x 600.0 -2 | | | | | | paintChild RenderPadding at Point(40.0, 20.0) +2 | | | | | | paintChild RenderPadding at Point(40.0, 16.0) 2 | | | | | | | TestDisplayList() constructor: 800.0 x 600.0 2 | | | | | | | paintChild RenderParagraph at Point(24.0, 0.0) 2 | | | | | | | | TestDisplayList() constructor: 800.0 x 600.0 -- GitLab