Bullet & ordered lists

This commit is contained in:
Dimitry Ivanov 2017-05-11 21:03:46 +03:00
parent c3b6c1ce39
commit 7f5db84bbe
6 changed files with 283 additions and 65 deletions

View File

@ -13,9 +13,15 @@
<h1>Yo!
Omg
ddffdg
</h1>
1. First
2. Second
3. Third
* Interesting
4. Forth
## Unordered list
@ -35,6 +41,41 @@ ddffdg
* it's also here
2. and this
3. and that
1. Another one nested this time and a lot of text here, well, at least some to check how multiline will be handled
1. And it goes on and on
2. And it goes on and on
3. And it goes on and on
4. And it goes on and on
5. And it goes on and on
6. And it goes on and on
7. And it goes on and on
8. And it goes on and on
9. And it goes on and on
10. And it goes on and on
11. And it goes on and on
12. And it goes on and on
13. And it goes on and on
14. And it goes on and on
15. And it goes on and on
16. And it goes on and on
17. And it goes on and on
18. And it goes on and on
19. And it goes on and on
20. And it goes on and on
21. And it goes on and on
22. And it goes on and on
23. And it goes on and on
24. And it goes on and on
25. And it goes on and on
26. And it goes on and on
27. And it goes on and on
28. And it goes on and on
29. And it goes on and on
30. And it goes on and on
31. And it goes on and on
32. And it goes on and on
33. And it goes on and on
### Quoted list

View File

@ -7,6 +7,7 @@ import ru.noties.markwon.spans.BlockQuoteSpan;
import ru.noties.markwon.spans.BulletListItemSpan;
import ru.noties.markwon.spans.CodeSpan;
import ru.noties.markwon.spans.HeadingSpan;
import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
public class SpannableConfiguration {
@ -25,6 +26,7 @@ public class SpannableConfiguration {
private final BulletListItemSpan.Config bulletListConfig;
private final HeadingSpan.Config headingConfig;
private final ThematicBreakSpan.Config thematicConfig;
private final OrderedListItemSpan.Config orderedListConfig;
private SpannableConfiguration(Builder builder) {
this.blockQuoteConfig = builder.blockQuoteConfig;
@ -32,6 +34,7 @@ public class SpannableConfiguration {
this.bulletListConfig = builder.bulletListConfig;
this.headingConfig = builder.headingConfig;
this.thematicConfig = builder.thematicConfig;
this.orderedListConfig = builder.orderedListConfig;
}
public BlockQuoteSpan.Config getBlockQuoteConfig() {
@ -54,6 +57,10 @@ public class SpannableConfiguration {
return thematicConfig;
}
public OrderedListItemSpan.Config getOrderedListConfig() {
return orderedListConfig;
}
public static class Builder {
private final Context context;
@ -62,6 +69,7 @@ public class SpannableConfiguration {
private BulletListItemSpan.Config bulletListConfig;
private HeadingSpan.Config headingConfig;
private ThematicBreakSpan.Config thematicConfig;
private OrderedListItemSpan.Config orderedListConfig;
public Builder(Context context) {
this.context = context;
@ -92,11 +100,17 @@ public class SpannableConfiguration {
return this;
}
public Builder setOrderedListConfig(@NonNull OrderedListItemSpan.Config orderedListConfig) {
this.orderedListConfig = orderedListConfig;
return this;
}
// todo, change to something more reliable
// todo, must mention that bullet/ordered/quote must have the same margin (or maybe we can just enforce it?)
public SpannableConfiguration build() {
if (blockQuoteConfig == null) {
blockQuoteConfig = new BlockQuoteSpan.Config(
px(16),
px(24),
0,
0
);
@ -107,7 +121,7 @@ public class SpannableConfiguration {
.build();
}
if (bulletListConfig == null) {
bulletListConfig = new BulletListItemSpan.Config(0, px(16), px(1));
bulletListConfig = new BulletListItemSpan.Config(px(24), 0, px(8), px(1));
}
if (headingConfig == null) {
headingConfig = new HeadingSpan.Config(px(1), 0);
@ -115,6 +129,9 @@ public class SpannableConfiguration {
if (thematicConfig == null) {
thematicConfig = new ThematicBreakSpan.Config(0, px(2));
}
if (orderedListConfig == null) {
orderedListConfig = new OrderedListItemSpan.Config(px(24), 0);
}
return new SpannableConfiguration(this);
}

View File

@ -16,7 +16,6 @@ import org.commonmark.node.FencedCodeBlock;
import org.commonmark.node.HardLineBreak;
import org.commonmark.node.Heading;
import org.commonmark.node.HtmlBlock;
import org.commonmark.node.Image;
import org.commonmark.node.ListBlock;
import org.commonmark.node.ListItem;
import org.commonmark.node.Node;
@ -33,6 +32,8 @@ import ru.noties.markwon.spans.BulletListItemSpan;
import ru.noties.markwon.spans.CodeSpan;
import ru.noties.markwon.spans.EmphasisSpan;
import ru.noties.markwon.spans.HeadingSpan;
import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.SimpleLeadingMarginSpan;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
@ -54,13 +55,13 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(Text text) {
Debug.i(text);
// Debug.i(text);
builder.append(text.getLiteral());
}
@Override
public void visit(StrongEmphasis strongEmphasis) {
Debug.i(strongEmphasis);
// Debug.i(strongEmphasis);
final int length = builder.length();
visitChildren(strongEmphasis);
setSpan(length, new StrongEmphasisSpan());
@ -68,7 +69,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(Emphasis emphasis) {
Debug.i(emphasis);
// Debug.i(emphasis);
final int length = builder.length();
visitChildren(emphasis);
setSpan(length, new EmphasisSpan());
@ -77,7 +78,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(BlockQuote blockQuote) {
Debug.i(blockQuote);
// Debug.i(blockQuote);
newLine();
if (blockQuoteIndent != 0) {
@ -106,7 +107,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(Code code) {
Debug.i(code);
// Debug.i(code);
final int length = builder.length();
@ -125,7 +126,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(FencedCodeBlock fencedCodeBlock) {
Debug.i(fencedCodeBlock);
// Debug.i(fencedCodeBlock);
newLine();
@ -141,33 +142,35 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
builder.append('\n');
}
@Override
public void visit(Image image) {
Debug.i(image);
final int length = builder.length();
visitChildren(image);
if (length == builder.length()) {
// nothing is added, and we need at least one symbol
builder.append(' ');
}
// @Override
// public void visit(Image image) {
//
// Debug.i(image);
//
//// final int length = builder.length();
// final TestDrawable drawable = new TestDrawable();
// final DrawableSpan span = new DrawableSpan(drawable);
// builder.append(" ");
// builder.setSpan(span, length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//
// visitChildren(image);
//
//// if (length == builder.length()) {
//// // nothing is added, and we need at least one symbol
//// builder.append(' ');
//// }
// }
@Override
public void visit(BulletList bulletList) {
Debug.i(bulletList);
visitList(bulletList);
}
@Override
public void visit(OrderedList orderedList) {
visitList(orderedList);
}
private void visitList(Node node) {
Debug.i(node);
newLine();
visitChildren(bulletList);
visitChildren(node);
newLine();
if (listLevel == 0 && blockQuoteIndent == 0) {
builder.append('\n');
@ -180,16 +183,67 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
Debug.i(listItem);
final int length = builder.length();
blockQuoteIndent += 1;
listLevel += 1;
visitChildren(listItem);
// todo, can be a bullet list & ordered list (with leading numbers... looks like we need to `draw` numbers...
final Node parent = listItem.getParent();
if (parent instanceof OrderedList) {
// // let's build ordered number
// final StringBuilder lead = new StringBuilder();
// Node p = parent;
// while (p != null && p instanceof OrderedList) {
// lead.insert(0, ((OrderedList) p).getDelimiter());
// lead.insert(0, ((OrderedList) p).getStartNumber());
// p = p.getParent();
// if (p instanceof ListItem) {
// p = p.getParent();
// }
// }
//
// builder.append(lead)
// .append('\u00a0');
//
// blockQuoteIndent -= 1;
final int start = ((OrderedList) parent).getStartNumber();
visitChildren(listItem);
setSpan(length, new OrderedListItemSpan(
configuration.getOrderedListConfig(),
String.valueOf(start) + "." + '\u00a0',
blockQuoteIndent,
length
));
// blockQuoteIndent += 1;
// if (listLevel != 1) {
// setSpan(length, new SimpleLeadingMarginSpan(32));
// }
// after we have visited the children increment start number
final OrderedList orderedList = (OrderedList) parent;
orderedList.setStartNumber(orderedList.getStartNumber() + 1);
} else {
visitChildren(listItem);
// if we are inside orderedList increase the margin?
setSpan(length, new BulletListItemSpan(
configuration.getBulletListConfig(),
blockQuoteIndent,
listLevel - 1,
length
));
}
blockQuoteIndent -= 1;
listLevel -= 1;
@ -199,7 +253,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(ThematicBreak thematicBreak) {
Debug.i(thematicBreak);
// Debug.i(thematicBreak);
newLine();
@ -211,27 +265,10 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
builder.append('\n');
}
@Override
public void visit(OrderedList orderedList) {
Debug.i(orderedList);
newLine();
// Debug.i(orderedList, orderedList.getDelimiter(), orderedList.getStartNumber());
// todo, ordering numbers
super.visit(orderedList);
newLine();
if (listLevel == 0 && blockQuoteIndent == 0) {
builder.append('\n');
}
}
@Override
public void visit(Heading heading) {
Debug.i(heading);
// Debug.i(heading);
newLine();
@ -264,7 +301,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override
public void visit(CustomNode customNode) {
Debug.i(customNode);
// Debug.i(customNode);
if (customNode instanceof Strikethrough) {
final int length = builder.length();
@ -280,7 +317,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final boolean inTightList = isInTightList(paragraph);
Debug.i(paragraph, inTightList, listLevel);
// Debug.i(paragraph, inTightList, listLevel);
if (!inTightList) {
newLine();
@ -326,4 +363,23 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
}
return false;
}
private static String dump(Node node) {
final StringBuilder builder = new StringBuilder();
node.accept(new DumpVisitor(builder));
return builder.toString();
}
private static class DumpVisitor extends AbstractVisitor {
private final StringBuilder builder;
DumpVisitor(StringBuilder builder) {
this.builder = builder;
}
@Override
public void visit(Text text) {
builder.append(text.getLiteral());
}
}
}

View File

@ -17,14 +17,20 @@ public class BulletListItemSpan implements LeadingMarginSpan {
public static class Config {
final int bulletColor; // by default uses text color
final int marginWidth;
final int bulletColor; // by default uses text color
final int bulletSide;
final int bulletStrokeWidth;
// from 0 but it makes sense to provide something wider
public Config(@ColorInt int bulletColor, @IntRange(from = 0) int marginWidth, int bulletStrokeWidth) {
this.bulletColor = bulletColor;
public Config(
@IntRange(from = 0) int marginWidth,
@ColorInt int bulletColor,
@IntRange(from = 0) int bulletSide,
@IntRange(from = 0) int bulletStrokeWidth) {
this.marginWidth = marginWidth;
this.bulletColor = bulletColor;
this.bulletSide = bulletSide;
this.bulletStrokeWidth = bulletStrokeWidth;
}
}
@ -58,7 +64,7 @@ public class BulletListItemSpan implements LeadingMarginSpan {
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
// if there was a line break, we don't need to draw it
// if there was a line break, we don't need to draw anything
if (this.start != start) {
return;
}
@ -88,7 +94,14 @@ public class BulletListItemSpan implements LeadingMarginSpan {
final int width = config.marginWidth;
final int height = bottom - top;
final int side = Math.min(config.marginWidth, height) / 2;
final int min = Math.min(config.marginWidth, height) / 2;
final int side;
if (config.bulletSide == 0
|| config.bulletSide > min) {
side = min;
} else {
side = config.bulletSide;
}
final int marginLeft = (width - side) / 2;
final int marginTop = (height - side) / 2;

View File

@ -0,0 +1,66 @@
package ru.noties.markwon.spans;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.text.Layout;
import android.text.style.LeadingMarginSpan;
public class OrderedListItemSpan implements LeadingMarginSpan {
public static class Config {
final int marginWidth; // by default 0
final int numberColor; // by default color of the main text
public Config(@IntRange(from = 0) int marginWidth, @ColorInt int numberColor) {
this.marginWidth = marginWidth;
this.numberColor = numberColor;
}
}
private final Config config;
private final String number;
private final int blockIndent;
private final int start;
public OrderedListItemSpan(
@NonNull Config config,
@NonNull String number,
@IntRange(from = 0) int blockIndent,
@IntRange(from = 0) int start
) {
this.config = config;
this.number = number;
this.blockIndent = blockIndent;
this.start = start;
}
@Override
public int getLeadingMargin(boolean first) {
return config.marginWidth;
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
// if there was a line break, we don't need to draw anything
if (this.start != start) {
return;
}
if (config.numberColor != 0) {
p.setColor(config.numberColor);
}
final int width = config.marginWidth;
final int numberWidth = (int) (p.measureText(number) + .5F);
final int numberX = (width * blockIndent) - numberWidth;
final int numberY = bottom - ((bottom - top) / 2) - (int) ((p.descent() + p.ascent()) / 2);
c.drawText(number, numberX, numberY, p);
}
}

View File

@ -0,0 +1,25 @@
package ru.noties.markwon.spans;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Layout;
import android.text.style.LeadingMarginSpan;
public class SimpleLeadingMarginSpan implements LeadingMarginSpan {
private final int margin;
public SimpleLeadingMarginSpan(int margin) {
this.margin = margin;
}
@Override
public int getLeadingMargin(boolean first) {
return margin;
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
// no op
}
}