diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/widget/StringWidget.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/widget/StringWidget.java new file mode 100644 index 0000000..11d5ebc --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/widget/StringWidget.java @@ -0,0 +1,17 @@ +package org.codejive.twinkle.core.widget; + +import org.codejive.twinkle.core.text.Canvas; +import org.codejive.twinkle.util.StyledIterator; + +public interface StringWidget extends Widget { + + CharSequence render(); + + default void render(Canvas canvas) { + StyledIterator iter = StyledIterator.of(render()); + int y = 0; + while (iter.hasNext()) { + canvas.putStringAt(0, y++, iter); + } + } +} diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/List.java b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/List.java new file mode 100644 index 0000000..9053074 --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/List.java @@ -0,0 +1,38 @@ +package org.codejive.twinkle.widgets.list; + +import org.codejive.twinkle.core.util.Size; +import org.codejive.twinkle.core.widget.StringWidget; +import org.codejive.twinkle.util.Printable; +import org.jspecify.annotations.NonNull; + +public class List implements StringWidget { + private @NonNull ListModel list; + private @NonNull ListRenderer renderer; + + protected List(@NonNull ListModel list, @NonNull ListRenderer renderer) { + this.list = list; + this.renderer = renderer; + } + + @Override + public @NonNull Size size() { + return null; + } + + @Override + public CharSequence render() { + return renderer.render(list, 0, list.items().size()); + } + + public static @NonNull List create() { + return new List(ListModel.ofStrings(), ListRenderer.create()); + } + + public static @NonNull List ofStrings(String... texts) { + return new List(ListModel.ofStrings(texts), ListRenderer.create()); + } + + public static @NonNull List ofPrintables(Printable... texts) { + return new List(ListModel.ofPrintables(texts), ListRenderer.create()); + } +} diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListConfig.java b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListConfig.java new file mode 100644 index 0000000..42b673f --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListConfig.java @@ -0,0 +1,12 @@ +package org.codejive.twinkle.widgets.list; + +import org.codejive.twinkle.ansi.Color; +import org.codejive.twinkle.ansi.Style; +import org.jspecify.annotations.NonNull; + +public class ListConfig { + public @NonNull Color listbackgroundColor = Color.DEFAULT; + public @NonNull Style itemStyle = Style.DEFAULT; + public @NonNull Style selectedItemStyle = Style.INVERSE; + public @NonNull Style highlightedItemStyle = Style.FAINT; +} diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListItem.java b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListItem.java new file mode 100644 index 0000000..c198800 --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListItem.java @@ -0,0 +1,23 @@ +package org.codejive.twinkle.widgets.list; + +import org.codejive.twinkle.util.Printable; + +public class ListItem { + public Printable content; + public boolean selected; + public boolean highlighted; + + protected ListItem(Printable content, boolean selected, boolean highlighted) { + this.content = content; + this.selected = selected; + this.highlighted = highlighted; + } + + public static ListItem of(Printable content) { + return new ListItem(content, false, false); + } + + public static ListItem of(Printable content, boolean selected, boolean highlighted) { + return new ListItem(content, selected, highlighted); + } +} diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListModel.java b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListModel.java new file mode 100644 index 0000000..6bcacb1 --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListModel.java @@ -0,0 +1,53 @@ +package org.codejive.twinkle.widgets.list; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.codejive.twinkle.core.text.Line; +import org.codejive.twinkle.util.Printable; + +public class ListModel { + private final List items; + + public ListModel() { + this.items = new ArrayList<>(); + } + + public List items() { + return items; + } + + public ListModel add(String text) { + items.add(ListItem.of(Line.of(text))); + return this; + } + + public ListModel add(Printable text) { + items.add(ListItem.of(text)); + return this; + } + + public static ListModel ofStrings(String... texts) { + return ofStrings(Arrays.asList(texts)); + } + + public static ListModel ofStrings(List texts) { + ListModel model = new ListModel(); + for (String text : texts) { + model.add(text); + } + return model; + } + + public static ListModel ofPrintables(Printable... texts) { + return ofPrintables(Arrays.asList(texts)); + } + + public static ListModel ofPrintables(List texts) { + ListModel model = new ListModel(); + for (Printable text : texts) { + model.add(text); + } + return model; + } +} diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListRenderer.java b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListRenderer.java new file mode 100644 index 0000000..2919c38 --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/list/ListRenderer.java @@ -0,0 +1,39 @@ +package org.codejive.twinkle.widgets.list; + +import java.util.stream.Collectors; +import org.codejive.twinkle.ansi.Ansi; +import org.jspecify.annotations.NonNull; + +public class ListRenderer { + private final @NonNull ListConfig config; + + protected ListRenderer(@NonNull ListConfig config) { + this.config = config; + } + + public CharSequence render(@NonNull ListModel list, int startIndex, int count) { + int endIndex = Math.min(startIndex + count, list.items().size()); + return list.items().subList(startIndex, endIndex).stream() + .map(this::renderItem) + .collect(Collectors.joining("\n")); + } + + public CharSequence renderItem(@NonNull ListItem item) { + String content = + Ansi.STYLE_RESET + + config.listbackgroundColor.toAnsiBg() + + config.itemStyle.toAnsiString(); + if (item.selected) { + content += config.selectedItemStyle.toAnsiString(); + } + if (item.highlighted) { + content += config.highlightedItemStyle.toAnsiString(); + } + content += item.content.toAnsiString(); + return content; + } + + public static @NonNull ListRenderer create() { + return new ListRenderer(new ListConfig()); + } +} diff --git a/twinkle-core/src/test/java/examples/ListDemo.java b/twinkle-core/src/test/java/examples/ListDemo.java new file mode 100644 index 0000000..dae2bde --- /dev/null +++ b/twinkle-core/src/test/java/examples/ListDemo.java @@ -0,0 +1,26 @@ +// java +package examples; + +import org.codejive.twinkle.ansi.Ansi; +import org.codejive.twinkle.core.text.Buffer; +import org.codejive.twinkle.core.text.Line; +import org.codejive.twinkle.widgets.Framed; +import org.codejive.twinkle.widgets.list.List; + +public class ListDemo { + public static void main(String[] args) throws InterruptedException { + List l = + List.ofStrings( + "First Item", "Second Item", "Third Item", "Fourth Item", "Fifth Item"); + Framed f = Framed.of(l).title(Line.of(" Simple List ")); + Buffer buf = Buffer.of(42, 22); + + System.out.print(Ansi.hideCursor()); + try { + f.render(buf); + System.out.println(buf.toAnsiString()); + } finally { + System.out.print(Ansi.showCursor()); + } + } +}