Bullet & ordered lists
This commit is contained in:
parent
c3b6c1ce39
commit
7f5db84bbe
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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(' ');
|
||||
}
|
||||
|
||||
|
||||
//// 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);
|
||||
}
|
||||
// @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(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...
|
||||
setSpan(length, new BulletListItemSpan(
|
||||
configuration.getBulletListConfig(),
|
||||
blockQuoteIndent,
|
||||
listLevel - 1,
|
||||
length
|
||||
));
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user