From c225d6fa8e966760ace00a6a289dfdb7986c34ac Mon Sep 17 00:00:00 2001 From: Mark Raynsford Date: Sat, 25 Nov 2023 10:09:30 +0000 Subject: [PATCH] Initial read-only text area Affects: https://github.com/io7m/jsycamore/issues/14 --- .../api/components/SyComponentType.java | 15 + .../components/SyTextAreaReadableType.java | 7 + .../api/components/SyTextAreaType.java | 6 + .../io7m/jsycamore/api/text/SyFontType.java | 33 ++ .../api/text/SyTextSectionLineType.java | 41 +++ .../jsycamore/awt/internal/SyFontAWT.java | 69 ++++ .../components/standard/SyTextArea.java | 187 +++++++++++ .../io7m/jsycamore/tests/SyFontAWTTest.java | 87 +++++ .../com/io7m/jsycamore/tests/SyMenuDemo.java | 2 - .../io7m/jsycamore/tests/SyTextAreaDemo.java | 275 ++++++++++++++++ .../io7m/jsycamore/tests/SyTextAreaTest.java | 60 ++++ .../jsycamore/tests/SyTextLayoutDemo.java | 296 ++++++++++++++++++ .../io7m/jsycamore/tests/SyWindowDemo.java | 3 - .../com/io7m/jsycamore/tests/arctic.txt | 1 + .../com/io7m/jsycamore/tests/lorem.txt | 9 + .../com/io7m/jsycamore/tests/loremShort.txt | 1 + .../primal/internal/SyPrimalAbstract.java | 7 +- .../primal/internal/SyPrimalTextArea.java | 146 +++++++++ .../primal/internal/SyPrimalTextView.java | 2 +- .../theme/primal/internal/SyThemePrimal.java | 6 +- .../src/main/java/module-info.java | 2 - 21 files changed, 1242 insertions(+), 13 deletions(-) create mode 100644 com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextSectionLineType.java create mode 100644 com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyTextArea.java create mode 100644 com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyFontAWTTest.java create mode 100644 com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaDemo.java create mode 100644 com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaTest.java create mode 100644 com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextLayoutDemo.java create mode 100644 com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/arctic.txt create mode 100644 com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/lorem.txt create mode 100644 com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/loremShort.txt create mode 100644 com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextArea.java diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyComponentType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyComponentType.java index a401fddc..d62a0604 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyComponentType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyComponentType.java @@ -31,6 +31,7 @@ import com.io7m.jsycamore.api.windows.SyWindowViewportAccumulatorType; import com.io7m.jtensors.core.parameterized.vectors.PVector2I; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -119,6 +120,20 @@ Optional componentForWindowRelative( SyWindowViewportAccumulatorType context, SyComponentQuery query); + /** + * A convenience function to remove all child components of this component. + */ + + default void childrenClear() + { + final var children = + List.copyOf(this.node().children()); + + for (final var child : children) { + this.node().childRemove(child); + } + } + /** * A convenience function to add a component as a child of this component. * diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaReadableType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaReadableType.java index ef55adec..c3bec719 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaReadableType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaReadableType.java @@ -16,6 +16,7 @@ package com.io7m.jsycamore.api.components; +import com.io7m.jattribute.core.AttributeReadableType; import com.io7m.jsycamore.api.themes.SyThemeClassNameType; import java.util.List; @@ -34,4 +35,10 @@ default List themeClassesDefaultForComponent() { return List.of(TEXT_AREA); } + + /** + * @return The text sections within the area + */ + + AttributeReadableType> textSections(); } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaType.java index fc147da5..be0d520e 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextAreaType.java @@ -23,5 +23,11 @@ public interface SyTextAreaType extends SyTextAreaReadableType, SyComponentType { + /** + * Append a text section to the end of the text area. + * + * @param section The section + */ + void textSectionAppend(String section); } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java index c802f1c8..770ea634 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java @@ -16,6 +16,8 @@ package com.io7m.jsycamore.api.text; +import java.util.List; + /** * A font. */ @@ -55,4 +57,35 @@ public interface SyFontType */ SyFontDescription description(); + + /** + * Split the given text into lines based on the given wrapping width. + * + * @param text The text + * @param width The width + * + * @return The split lines + */ + + List textLayout( + String text, + int width); + + /** + * Split the given texts into lines based on the given wrapping width. + * + * @param texts The texts + * @param width The width + * + * @return The split lines + */ + + default List textLayoutMultiple( + final List texts, + final int width) + { + return texts.stream() + .flatMap(s -> this.textLayout(s, width).stream()) + .toList(); + } } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextSectionLineType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextSectionLineType.java new file mode 100644 index 00000000..5e3d1e2a --- /dev/null +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextSectionLineType.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2023 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.api.text; + +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; + +/** + * A line that has been produced by splitting a text section into multiple lines + * based on a wrapping width. + */ + +public interface SyTextSectionLineType +{ + /** + * @return The size of the text line + */ + + PAreaSizeI size(); + + /** + * @return The actual line text + */ + + String text(); +} diff --git a/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyFontAWT.java b/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyFontAWT.java index 8aff2b0a..89688c6e 100644 --- a/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyFontAWT.java +++ b/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyFontAWT.java @@ -17,11 +17,19 @@ package com.io7m.jsycamore.awt.internal; +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; import com.io7m.jsycamore.api.text.SyFontDescription; import com.io7m.jsycamore.api.text.SyFontType; +import com.io7m.jsycamore.api.text.SyTextSectionLineType; import java.awt.Font; import java.awt.FontMetrics; +import java.awt.font.LineBreakMeasurer; +import java.awt.font.TextAttribute; +import java.text.AttributedString; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; /** @@ -80,4 +88,65 @@ public SyFontDescription description() { return this.description; } + + @Override + public List textLayout( + final String text, + final int width) + { + Objects.requireNonNull(text, "text"); + + if (text.isEmpty()) { + return List.of( + new SectionLine(PAreaSizeI.of(0, this.textHeight()), "") + ); + } + + final var attributedString = new AttributedString(text); + attributedString.addAttribute(TextAttribute.FONT, this.font); + + final var breaker = + new LineBreakMeasurer( + attributedString.getIterator(), + this.metrics.getFontRenderContext() + ); + + final var fWidth = (float) width; + + var indexThen = 0; + + final var results = + new LinkedList(); + + while (true) { + final var layout = breaker.nextLayout(fWidth); + if (layout == null) { + break; + } + + final var indexNow = + breaker.getPosition(); + final var bounds = + layout.getBounds(); + + final var line = + new SectionLine( + PAreaSizeI.of((int) Math.ceil(bounds.getWidth()), this.textHeight()), + text.substring(indexThen, indexNow) + ); + + results.add(line); + indexThen = indexNow; + } + + return results; + } + + private record SectionLine( + PAreaSizeI size, + String text) + implements SyTextSectionLineType + { + + } } diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyTextArea.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyTextArea.java new file mode 100644 index 00000000..d99bbd87 --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyTextArea.java @@ -0,0 +1,187 @@ +/* + * Copyright © 2023 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.components.standard; + +import com.io7m.jattribute.core.AttributeReadableType; +import com.io7m.jattribute.core.AttributeType; +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.components.SyConstraints; +import com.io7m.jsycamore.api.components.SyTextAreaType; +import com.io7m.jsycamore.api.events.SyEventConsumed; +import com.io7m.jsycamore.api.events.SyEventType; +import com.io7m.jsycamore.api.layout.SyLayoutContextType; +import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; +import com.io7m.jsycamore.api.themes.SyThemeClassNameType; +import com.io7m.jtensors.core.parameterized.vectors.PVector2I; + +import java.util.LinkedList; +import java.util.List; + +import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED; +import static java.lang.Math.min; + +/** + * A text area. + */ + +public final class SyTextArea + extends SyComponentAbstract implements SyTextAreaType +{ + private static final int INTERNAL_TEXT_PADDING = 16; + private static final int INTERNAL_TEXT_PADDING_DOUBLE = + INTERNAL_TEXT_PADDING + INTERNAL_TEXT_PADDING; + + private final AttributeType> textSections; + private final SyPackVertical textContainer; + private boolean viewsUpToDate; + + /** + * A text area. + * + * @param inThemeClassesExtra The extra theme classes, if any + */ + + public SyTextArea( + final List inThemeClassesExtra) + { + super(inThemeClassesExtra); + + final var attributes = + SyComponentAttributes.get(); + this.textSections = + attributes.create(List.of()); + + this.textContainer = new SyPackVertical(inThemeClassesExtra); + this.textSections.subscribe(this::invalidateViews); + this.size().subscribe(this::invalidateViews); + + this.childAdd(this.textContainer); + } + + private void invalidateViews( + final Object ignored0, + final Object ignored1) + { + this.viewsUpToDate = false; + } + + @Override + protected SyEventConsumed onEvent( + final SyEventType event) + { + return EVENT_NOT_CONSUMED; + } + + @Override + public AttributeReadableType> textSections() + { + return this.textSections; + } + + @Override + public PAreaSizeI layout( + final SyLayoutContextType layoutContext, + final SyConstraints constraints) + { + /* + * Set this text area to the maximum allowed size. + */ + + final var sizeLimit = + this.sizeUpperLimit().get(); + + final var limitedConstraints = + new SyConstraints( + constraints.sizeMinimumX(), + constraints.sizeMinimumY(), + min(constraints.sizeMaximumX(), sizeLimit.sizeX()), + min(constraints.sizeMaximumY(), sizeLimit.sizeY()) + ); + + final PAreaSizeI newSize = + limitedConstraints.sizeMaximum(); + + this.setSize(newSize); + + /* + * The text container is the current text area size, plus a margin. + */ + + final var internalAreaPosition = + PVector2I.of( + INTERNAL_TEXT_PADDING, + INTERNAL_TEXT_PADDING + ); + + final var internalArea = + PAreaSizeI.of( + Math.max(0, newSize.sizeX() - INTERNAL_TEXT_PADDING_DOUBLE), + Math.max(0, newSize.sizeY() - INTERNAL_TEXT_PADDING_DOUBLE) + ); + + this.textContainer.position() + .set(internalAreaPosition); + this.textContainer.size() + .set(internalArea); + + /* + * If the internal text views aren't up-to-date (perhaps because the + * text changed), then remove and create new views, and then tell the + * text container to execute a layout. + */ + + if (!this.viewsUpToDate) { + this.textContainer.childrenClear(); + + final var themeComponent = + layoutContext.themeCurrent() + .findForComponent(this); + + final var font = + themeComponent.font(layoutContext, this); + + final var lines = + font.textLayoutMultiple( + this.textSections.get(), + internalArea.sizeX() + ); + + for (final var line : lines) { + final var textView = new SyTextView(); + textView.setSize(line.size()); + textView.setText(line.text()); + textView.setMouseQueryAccepting(false); + this.textContainer.childAdd(textView); + } + + this.textContainer.layout(layoutContext, limitedConstraints); + this.viewsUpToDate = true; + } + + return newSize; + } + + @Override + public void textSectionAppend( + final String section) + { + final var appended = new LinkedList<>(this.textSections.get()); + appended.add(section); + this.textSections.set(List.copyOf(appended)); + } +} diff --git a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyFontAWTTest.java b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyFontAWTTest.java new file mode 100644 index 00000000..078308a9 --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyFontAWTTest.java @@ -0,0 +1,87 @@ +/* + * Copyright © 2023 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.tests; + +import com.io7m.jsycamore.api.text.SyFontDescription; +import com.io7m.jsycamore.api.text.SyFontDirectoryType; +import com.io7m.jsycamore.api.text.SyFontException; +import com.io7m.jsycamore.api.text.SyTextSectionLineType; +import com.io7m.jsycamore.awt.internal.SyFontAWT; +import com.io7m.jsycamore.awt.internal.SyFontDirectoryAWT; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static com.io7m.jsycamore.api.text.SyFontStyle.REGULAR; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public final class SyFontAWTTest +{ + private SyFontDirectoryType fonts; + private SyFontAWT font; + + @BeforeEach + public void setup() + throws SyFontException + { + this.fonts = + SyFontDirectoryAWT.createFromServiceLoader(); + this.font = + this.fonts.get( + new SyFontDescription("DejaVu Sans", REGULAR, 12) + ); + } + + @Test + public void testLayout() + throws Exception + { + try (var s = SyFontAWTTest.class.getResourceAsStream( + "/com/io7m/jsycamore/tests/arctic.txt")) { + final var text = new String(s.readAllBytes(), StandardCharsets.UTF_8); + + final var r = + new ArrayList<>(this.font.textLayoutMultiple(List.of(text), 320)); + + assertEquals(26, r.size()); + check(r, "For three hundred years explorers have been "); + check(r, "active in pushing aside the realms of the unknown "); + check(r, "towards the north pole; but the equally interesting "); + check(r, "south pole has, during all this time, been almost "); + check(r, "wholly neglected. There have been expeditions to "); + check(r, "the far south, but compared to arctic ventures "); + check(r, "they have been so few and their work within the "); + check(r, "polar circle has been so little that the results have "); + check(r, "been largely forgotten. It is not because valuable "); + check(r, "results have not been obtained in the antarctic, "); + check(r, "but because the popular interest in the arctic has "); + check(r, "completely overshadowed the reports of the "); + check(r, "antipodes. The search for the North-west and the "); + } + } + + private static void check( + final List lines, + final String expected) + { + assertEquals(expected, lines.remove(0).text()); + } +} diff --git a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyMenuDemo.java b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyMenuDemo.java index cc6d396b..cdf76f3d 100644 --- a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyMenuDemo.java +++ b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyMenuDemo.java @@ -16,7 +16,6 @@ package com.io7m.jsycamore.tests; -import com.io7m.jattribute.core.AttributeReadableType; import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; import com.io7m.jsycamore.api.mouse.SyMouseButton; import com.io7m.jsycamore.api.screens.SyScreenType; @@ -28,7 +27,6 @@ import com.io7m.jsycamore.awt.internal.SyAWTRenderer; import com.io7m.jsycamore.awt.internal.SyFontAWT; import com.io7m.jsycamore.awt.internal.SyFontDirectoryAWT; -import com.io7m.jsycamore.awt.internal.SyRendererType; import com.io7m.jsycamore.components.standard.SyButton; import com.io7m.jsycamore.components.standard.SyLayoutHorizontal; import com.io7m.jsycamore.components.standard.SyMenu; diff --git a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaDemo.java b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaDemo.java new file mode 100644 index 00000000..856b8f5c --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaDemo.java @@ -0,0 +1,275 @@ +/* + * Copyright © 2021 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jsycamore.tests; + +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.mouse.SyMouseButton; +import com.io7m.jsycamore.api.screens.SyScreenType; +import com.io7m.jsycamore.api.spaces.SySpaceViewportType; +import com.io7m.jsycamore.api.text.SyFontDirectoryType; +import com.io7m.jsycamore.api.themes.SyThemeType; +import com.io7m.jsycamore.api.windows.SyWindowType; +import com.io7m.jsycamore.awt.internal.SyAWTImageLoader; +import com.io7m.jsycamore.awt.internal.SyAWTRenderer; +import com.io7m.jsycamore.awt.internal.SyFontAWT; +import com.io7m.jsycamore.awt.internal.SyFontDirectoryAWT; +import com.io7m.jsycamore.awt.internal.SyRendererType; +import com.io7m.jsycamore.components.standard.SyLayoutMargin; +import com.io7m.jsycamore.components.standard.SyTextArea; +import com.io7m.jsycamore.theme.primal.SyThemePrimalFactory; +import com.io7m.jsycamore.vanilla.SyScreenFactory; +import com.io7m.jsycamore.vanilla.internal.SyLayoutContext; +import com.io7m.jtensors.core.parameterized.vectors.PVector2I; +import com.io7m.junreachable.UnreachableCodeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import java.util.concurrent.Executors; + +import static com.io7m.jsycamore.api.windows.SyWindowCloseBehaviour.HIDE_ON_CLOSE_BUTTON; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; + +public final class SyTextAreaDemo +{ + private static final Logger LOG = + LoggerFactory.getLogger(SyTextAreaDemo.class); + + private SyTextAreaDemo() + { + throw new UnreachableCodeException(); + } + + public static void main(final String[] args) + { + SwingUtilities.invokeLater(() -> { + try { + final var frame = new JFrame("WindowDemo"); + frame.setPreferredSize(new Dimension(800, 600)); + frame.getContentPane().add(new Canvas()); + frame.pack(); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setVisible(true); + } catch (final Exception e) { + throw new RuntimeException(e); + } + }); + } + + private static final class Canvas extends JPanel + { + private final SyFontDirectoryType fontDirectory; + private final SyRendererType renderer; + private final SyScreenType screen; + private final SyThemeType theme; + private final SyWindowType window0; + private final SyAWTImageLoader imageLoader; + private final List sections; + + Canvas() + throws Exception + { + this.setFocusable(true); + + this.fontDirectory = + SyFontDirectoryAWT.createFromServiceLoader(); + this.imageLoader = + new SyAWTImageLoader(); + + this.theme = + new SyThemePrimalFactory() + .create(); + + this.screen = + new SyScreenFactory().create( + this.theme, + this.fontDirectory, + PAreaSizeI.of(1024, 768) + ); + + this.window0 = + this.screen.windowCreate(640, 480); + + this.window0.closeButtonBehaviour() + .set(HIDE_ON_CLOSE_BUTTON); + + this.renderer = new SyAWTRenderer(this.fontDirectory, this.imageLoader); + // this.renderer = new SyBoundsOnlyRenderer(); + + final var executor = + Executors.newSingleThreadScheduledExecutor(runnable -> { + final var thread = new Thread(runnable); + thread.setName("SyWindowDemo.scheduler"); + thread.setDaemon(true); + return thread; + }); + + final var mouseAdapter = new MouseAdapter() + { + @Override + public void mousePressed( + final MouseEvent e) + { + SyTextAreaDemo.Canvas.this.screen.mouseDown( + PVector2I.of(e.getX(), e.getY()), + SyMouseButton.ofIndex(e.getButton() - 1)); + SyTextAreaDemo.Canvas.this.repaint(); + } + + @Override + public void mouseDragged( + final MouseEvent e) + { + SyTextAreaDemo.Canvas.this.screen.mouseMoved(PVector2I.of( + e.getX(), + e.getY())); + SyTextAreaDemo.Canvas.this.repaint(); + } + + @Override + public void mouseReleased( + final MouseEvent e) + { + SyTextAreaDemo.Canvas.this.screen.mouseUp( + PVector2I.of(e.getX(), e.getY()), + SyMouseButton.ofIndex(e.getButton() - 1)); + SyTextAreaDemo.Canvas.this.repaint(); + } + + @Override + public void mouseMoved( + final MouseEvent e) + { + final PVector2I position = + PVector2I.of(e.getX(), e.getY()); + SyTextAreaDemo.Canvas.this.screen.mouseMoved(position); + SyTextAreaDemo.Canvas.this.repaint(); + } + }; + + final var keyAdapter = new KeyAdapter() + { + @Override + public void keyPressed(final KeyEvent e) + { + System.out.println(e); + } + + @Override + public void keyReleased(final KeyEvent e) + { + System.out.println(e); + } + }; + + this.addMouseMotionListener(mouseAdapter); + this.addMouseListener(mouseAdapter); + this.addKeyListener(keyAdapter); + + this.addComponentListener(new ComponentAdapter() + { + @Override + public void componentResized(final ComponentEvent e) + { + SyTextAreaDemo.Canvas.this.screen.setSize( + PAreaSizeI.of( + e.getComponent().getWidth(), + e.getComponent().getHeight()) + ); + } + }); + + { + final var c = + SyTextLayoutDemo.class; + final var u = + c.getResource("/com/io7m/jsycamore/tests/arctic.txt"); + + try (var s = u.openStream()) { + this.sections = new String(s.readAllBytes(), UTF_8).lines().toList(); + } + } + + { + final var margin = new SyLayoutMargin(); + margin.setPaddingAll(8); + + final var textArea = new SyTextArea(List.of()); + for (final var section : this.sections) { + textArea.textSectionAppend(section); + } + + margin.childAdd(textArea); + this.window0.contentArea().childAdd(margin); + } + + executor.scheduleAtFixedRate(() -> { + SwingUtilities.invokeLater(() -> { + if (!this.screen.windowIsVisible(this.window0)) { + this.screen.windowShow(this.window0); + } + }); + }, 0L, 2L, SECONDS); + } + + @Override + public void paint(final Graphics g) + { + final var g2 = (Graphics2D) g; + + g2.setColor(Color.GRAY); + g2.fillRect(0, 0, this.getWidth(), this.getHeight()); + + g2.setPaint(new Color(144, 144, 144)); + + for (int x = 0; x < this.getWidth(); x += 32) { + g2.drawLine(x, 0, x, this.getHeight()); + } + for (int y = 0; y < this.getHeight(); y += 32) { + g2.drawLine(0, y, this.getWidth(), y); + } + + final var layoutContext = + new SyLayoutContext( + this.fontDirectory, + this.screen.theme() + ); + + final var windows = this.screen.windowsVisibleOrdered(); + for (int index = windows.size() - 1; index >= 0; --index) { + final var window = windows.get(index); + window.layout(layoutContext); + this.renderer.render(g2, this.screen, window); + } + } + } +} \ No newline at end of file diff --git a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaTest.java b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaTest.java new file mode 100644 index 00000000..86edccc1 --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextAreaTest.java @@ -0,0 +1,60 @@ +/* + * Copyright © 2022 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.tests; + +import com.io7m.jsycamore.api.windows.SyWindowClosed; +import com.io7m.jsycamore.api.windows.SyWindowID; +import com.io7m.jsycamore.components.standard.SyTextArea; +import com.io7m.jsycamore.components.standard.SyTextView; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public final class SyTextAreaTest extends SyComponentContract +{ + @BeforeEach + public void textViewSetup() + { + + } + + /** + * A text area doesn't accept window events. + */ + + @Test + public void testWindowEvents() + { + final var c = this.newComponent(); + assertEquals( + EVENT_NOT_CONSUMED, + c.eventSend(new SyWindowClosed(new SyWindowID(UUID.randomUUID()))) + ); + } + + @Override + protected SyTextArea newComponent() + { + return new SyTextArea(List.of()); + } +} diff --git a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextLayoutDemo.java b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextLayoutDemo.java new file mode 100644 index 00000000..4bbd737a --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyTextLayoutDemo.java @@ -0,0 +1,296 @@ +/* + * Copyright © 2021 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jsycamore.tests; + +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.mouse.SyMouseButton; +import com.io7m.jsycamore.api.screens.SyScreenType; +import com.io7m.jsycamore.api.spaces.SySpaceViewportType; +import com.io7m.jsycamore.api.text.SyFontDescription; +import com.io7m.jsycamore.api.text.SyFontDirectoryType; +import com.io7m.jsycamore.api.text.SyFontStyle; +import com.io7m.jsycamore.api.themes.SyThemeType; +import com.io7m.jsycamore.api.windows.SyWindowType; +import com.io7m.jsycamore.awt.internal.SyAWTImageLoader; +import com.io7m.jsycamore.awt.internal.SyAWTKeyCodeAdapter; +import com.io7m.jsycamore.awt.internal.SyAWTRenderer; +import com.io7m.jsycamore.awt.internal.SyFontAWT; +import com.io7m.jsycamore.awt.internal.SyFontDirectoryAWT; +import com.io7m.jsycamore.components.standard.SyLayoutMargin; +import com.io7m.jsycamore.components.standard.SyPackVertical; +import com.io7m.jsycamore.components.standard.SyTextView; +import com.io7m.jsycamore.theme.primal.SyThemePrimalFactory; +import com.io7m.jsycamore.vanilla.SyScreenFactory; +import com.io7m.jsycamore.vanilla.internal.SyLayoutContext; +import com.io7m.jtensors.core.parameterized.vectors.PVector2I; +import com.io7m.junreachable.UnreachableCodeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import java.util.concurrent.Executors; + +import static com.io7m.jsycamore.api.windows.SyWindowCloseBehaviour.HIDE_ON_CLOSE_BUTTON; +import static java.lang.Boolean.TRUE; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; + +public final class SyTextLayoutDemo +{ + private static final Logger LOG = + LoggerFactory.getLogger(SyTextLayoutDemo.class); + + private SyTextLayoutDemo() + { + throw new UnreachableCodeException(); + } + + public static void main(final String[] args) + { + SwingUtilities.invokeLater(() -> { + try { + final var frame = new JFrame("WindowDemo"); + frame.setPreferredSize(new Dimension(800, 600)); + frame.getContentPane().add(new Canvas()); + frame.pack(); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setVisible(true); + } catch (final Exception e) { + throw new RuntimeException(e); + } + }); + } + + private static final class Canvas extends JPanel + { + private final SyFontDirectoryType fontDirectory; + private final SyAWTRenderer renderer; + private final SyScreenType screen; + private final SyThemeType theme; + private final SyWindowType window0; + private final SyAWTImageLoader imageLoader; + private final SyPackVertical vertical; + private final List sections; + private final SyFontAWT font; + + Canvas() + throws Exception + { + this.setFocusable(true); + + this.fontDirectory = + SyFontDirectoryAWT.createFromServiceLoader(); + this.imageLoader = + new SyAWTImageLoader(); + + this.theme = + new SyThemePrimalFactory() + .create(); + + this.theme.values() + .setFont("text_font", new SyFontDescription( + "York Sans", + SyFontStyle.REGULAR, + 12 + )); + + this.theme.values() + .setFont("window_title_text_font", new SyFontDescription( + "York Sans", + SyFontStyle.REGULAR, + 12 + )); + + this.screen = + new SyScreenFactory().create( + this.theme, + this.fontDirectory, + PAreaSizeI.of(800, 600) + ); + + this.window0 = + this.screen.windowCreate(640, 480); + + this.window0.closeButtonBehaviour() + .set(HIDE_ON_CLOSE_BUTTON); + + this.renderer = new SyAWTRenderer(this.fontDirectory, this.imageLoader); + this.renderer.nodeRenderer() + .setTextAntialiasing(false); + + final var executor = + Executors.newSingleThreadScheduledExecutor(runnable -> { + final var thread = new Thread(runnable); + thread.setName("SyWindowDemo.scheduler"); + thread.setDaemon(true); + return thread; + }); + + final var mouseAdapter = new MouseAdapter() + { + @Override + public void mousePressed( + final MouseEvent e) + { + SyTextLayoutDemo.Canvas.this.screen.mouseDown( + PVector2I.of(e.getX(), e.getY()), + SyMouseButton.ofIndex(e.getButton() - 1)); + SyTextLayoutDemo.Canvas.this.repaint(); + } + + @Override + public void mouseDragged( + final MouseEvent e) + { + SyTextLayoutDemo.Canvas.this.screen.mouseMoved(PVector2I.of(e.getX(), e.getY())); + SyTextLayoutDemo.Canvas.this.repaint(); + } + + @Override + public void mouseReleased( + final MouseEvent e) + { + SyTextLayoutDemo.Canvas.this.screen.mouseUp( + PVector2I.of(e.getX(), e.getY()), + SyMouseButton.ofIndex(e.getButton() - 1)); + SyTextLayoutDemo.Canvas.this.repaint(); + } + + @Override + public void mouseMoved( + final MouseEvent e) + { + final PVector2I position = + PVector2I.of(e.getX(), e.getY()); + SyTextLayoutDemo.Canvas.this.screen.mouseMoved(position); + SyTextLayoutDemo.Canvas.this.repaint(); + } + }; + + final var keyAdapter = new SyAWTKeyCodeAdapter(this.screen); + this.addMouseMotionListener(mouseAdapter); + this.addMouseListener(mouseAdapter); + this.addKeyListener(keyAdapter); + + this.addComponentListener(new ComponentAdapter() + { + @Override + public void componentResized(final ComponentEvent e) + { + SyTextLayoutDemo.Canvas.this.screen.setSize( + PAreaSizeI.of( + e.getComponent().getWidth(), + e.getComponent().getHeight()) + ); + } + }); + + { + final var margin = new SyLayoutMargin(); + margin.setPaddingAll(16); + + this.vertical = new SyPackVertical(); + this.vertical.paddingBetween().set(0); + margin.childAdd(this.vertical); + + this.window0.decorated().set(TRUE); + this.window0.contentArea().childAdd(margin); + this.window0.title().set("Window Title"); + this.window0.positionSnapping().set(16); + this.window0.sizeSnapping().set(16); + } + + { + this.font = + this.fontDirectory.get( + new SyFontDescription( + "York Sans", + SyFontStyle.REGULAR, + 12 + )); + + final var c = + SyTextLayoutDemo.class; + final var u = + c.getResource("/com/io7m/jsycamore/tests/arctic.txt"); + + try (var s = u.openStream()) { + this.sections = new String(s.readAllBytes(), UTF_8).lines().toList(); + } + } + + executor.scheduleAtFixedRate(() -> { + SwingUtilities.invokeLater(() -> { + if (!this.screen.windowIsVisible(this.window0)) { + this.screen.windowShow(this.window0); + } + }); + }, 0L, 2L, SECONDS); + } + + @Override + public void paint(final Graphics g) + { + final var g2 = (Graphics2D) g; + + g2.setColor(Color.GRAY); + g2.fillRect(0, 0, this.getWidth(), this.getHeight()); + + g2.setPaint(new Color(144, 144, 144)); + + for (int x = 0; x < this.getWidth(); x += 32) { + g2.drawLine(x, 0, x, this.getHeight()); + } + for (int y = 0; y < this.getHeight(); y += 32) { + g2.drawLine(0, y, this.getWidth(), y); + } + + final var layoutContext = + new SyLayoutContext( + this.fontDirectory, + this.screen.theme() + ); + + this.vertical.childrenClear(); + for (final var line : this.font.textLayoutMultiple(this.sections, this.window0.size().get().sizeX() / 2)) { + final var t = new SyTextView(); + t.setText(line.text()); + t.setSize(line.size()); + this.vertical.childAdd(t); + } + + final var windows = this.screen.windowsVisibleOrdered(); + for (int index = windows.size() - 1; index >= 0; --index) { + final var window = windows.get(index); + window.layout(layoutContext); + this.renderer.render(g2, this.screen, window); + } + } + } +} \ No newline at end of file diff --git a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyWindowDemo.java b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyWindowDemo.java index 7250b0ae..db4bc393 100644 --- a/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyWindowDemo.java +++ b/com.io7m.jsycamore.tests/src/test/java/com/io7m/jsycamore/tests/SyWindowDemo.java @@ -30,7 +30,6 @@ import com.io7m.jsycamore.awt.internal.SyAWTRenderer; import com.io7m.jsycamore.awt.internal.SyFontAWT; import com.io7m.jsycamore.awt.internal.SyFontDirectoryAWT; -import com.io7m.jsycamore.awt.internal.SyRendererType; import com.io7m.jsycamore.components.standard.SyButton; import com.io7m.jsycamore.components.standard.SyImageView; import com.io7m.jsycamore.components.standard.SyLayoutHorizontal; @@ -54,8 +53,6 @@ import java.awt.Graphics2D; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Optional; diff --git a/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/arctic.txt b/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/arctic.txt new file mode 100644 index 00000000..baf6f3fe --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/arctic.txt @@ -0,0 +1 @@ +For three hundred years explorers have been active in pushing aside the realms of the unknown towards the north pole; but the equally interesting south pole has, during all this time, been almost wholly neglected. There have been expeditions to the far south, but compared to arctic ventures they have been so few and their work within the polar circle has been so little that the results have been largely forgotten. It is not because valuable results have not been obtained in the antarctic, but because the popular interest in the arctic has completely overshadowed the reports of the antipodes. The search for the North-west and the North-east passages, which commerce demanded to reach the trade of the Orient during the seventeenth and the early part of the eighteenth centuries, fixed the public eye persistently northward. This extended effort to find an easy path to the wealth of Asia was fruitless, but it was followed by a whale fishery, a sealing industry, and a fur trade, which has proven a priceless boon to mankind. As a result of these two periods of trade exploration, we have now entered upon a third stage, a period of scientific research which will not, and should not, end until the entire area is outlined in the growing annals of exact knowledge. diff --git a/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/lorem.txt b/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/lorem.txt new file mode 100644 index 00000000..6a16334f --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/lorem.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Feugiat in fermentum posuere urna nec tincidunt praesent semper. Morbi tristique senectus et netus et. In fermentum posuere urna nec tincidunt praesent semper feugiat. Tristique et egestas quis ipsum suspendisse ultrices gravida. Accumsan sit amet nulla facilisi morbi tempus. Et netus et malesuada fames ac turpis egestas integer eget. Aliquam purus sit amet luctus. Cras adipiscing enim eu turpis egestas pretium aenean. + +Tempor nec feugiat nisl pretium fusce id. Sit amet volutpat consequat mauris nunc congue nisi vitae. Mi proin sed libero enim sed. Tristique senectus et netus et malesuada fames ac. In arcu cursus euismod quis viverra nibh cras pulvinar. Mauris pharetra et ultrices neque ornare aenean euismod. Tellus mauris a diam maecenas. Cursus sit amet dictum sit amet justo donec. Aliquet enim tortor at auctor urna nunc id cursus metus. Urna condimentum mattis pellentesque id nibh tortor id. Hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Ornare arcu odio ut sem. Orci sagittis eu volutpat odio facilisis. A erat nam at lectus urna duis convallis convallis. Ipsum dolor sit amet consectetur adipiscing elit ut aliquam. + +Non arcu risus quis varius. Tristique risus nec feugiat in fermentum posuere. Pharetra magna ac placerat vestibulum lectus mauris ultrices eros in. Mauris rhoncus aenean vel elit scelerisque. In hendrerit gravida rutrum quisque non. Sed risus pretium quam vulputate dignissim. Magna ac placerat vestibulum lectus mauris ultrices. Sociis natoque penatibus et magnis dis parturient. Tellus integer feugiat scelerisque varius morbi enim nunc faucibus a. Donec adipiscing tristique risus nec feugiat in fermentum posuere urna. Nisl pretium fusce id velit ut tortor. Nec ullamcorper sit amet risus nullam eget felis eget. Viverra mauris in aliquam sem fringilla ut morbi. Integer malesuada nunc vel risus. + +Eu mi bibendum neque egestas congue quisque egestas. Sit amet consectetur adipiscing elit ut aliquam purus sit. Pharetra diam sit amet nisl suscipit adipiscing bibendum est ultricies. Congue nisi vitae suscipit tellus mauris a diam maecenas sed. Sollicitudin tempor id eu nisl nunc mi ipsum faucibus vitae. Blandit turpis cursus in hac habitasse platea. Eros donec ac odio tempor. Orci nulla pellentesque dignissim enim sit amet venenatis. Odio morbi quis commodo odio aenean. Nullam ac tortor vitae purus faucibus ornare. Turpis massa tincidunt dui ut ornare lectus sit amet est. Lacus vel facilisis volutpat est. Lectus quam id leo in vitae turpis. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Volutpat consequat mauris nunc congue. Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam malesuada. Sollicitudin aliquam ultrices sagittis orci. Vitae congue eu consequat ac felis donec et. + +Cras semper auctor neque vitae tempus quam pellentesque. Ut pharetra sit amet aliquam id diam maecenas ultricies mi. Posuere urna nec tincidunt praesent semper feugiat. Enim sed faucibus turpis in eu mi. Est velit egestas dui id ornare arcu odio ut. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae. Sed sed risus pretium quam vulputate dignissim suspendisse. Eu sem integer vitae justo eget. Egestas tellus rutrum tellus pellentesque eu tincidunt. Lacus viverra vitae congue eu consequat ac felis. Integer enim neque volutpat ac. Mauris a diam maecenas sed enim ut sem viverra aliquet. Sed felis eget velit aliquet sagittis id. Tortor vitae purus faucibus ornare suspendisse sed nisi. Orci a scelerisque purus semper eget duis. diff --git a/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/loremShort.txt b/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/loremShort.txt new file mode 100644 index 00000000..0a0c4259 --- /dev/null +++ b/com.io7m.jsycamore.tests/src/test/resources/com/io7m/jsycamore/tests/loremShort.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Feugiat in fermentum posuere urna nec tincidunt praesent semper. Morbi tristique senectus et netus et. In fermentum posuere urna nec tincidunt praesent semper feugiat. Tristique et egestas quis ipsum suspendisse ultrices gravida. Accumsan sit amet nulla facilisi morbi tempus. Et netus et malesuada fames ac turpis egestas integer eget. Aliquam purus sit amet luctus. Cras adipiscing enim eu turpis egestas pretium aenean. diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalAbstract.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalAbstract.java index f0fce26c..c9af574e 100644 --- a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalAbstract.java +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalAbstract.java @@ -76,8 +76,11 @@ public SyFontType font( Objects.requireNonNull(component, "component"); try { - return context.fonts() - .get(this.theme.values().font(SyPrimalValues.TEXT_FONT)); + final var themeValues = + this.theme.values(); + final var font = + themeValues.font(SyPrimalValues.TEXT_FONT); + return context.fonts().get(font); } catch (final SyThemeValueException | SyFontException e) { throw new IllegalStateException(e); } diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextArea.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextArea.java new file mode 100644 index 00000000..c05bf23c --- /dev/null +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextArea.java @@ -0,0 +1,146 @@ +/* + * Copyright © 2022 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jsycamore.theme.primal.internal; + +import com.io7m.jregions.core.parameterized.areas.PAreasI; +import com.io7m.jsycamore.api.components.SyComponentReadableType; +import com.io7m.jsycamore.api.components.SyTextAreaReadableType; +import com.io7m.jsycamore.api.rendering.SyEmbossedRectangle; +import com.io7m.jsycamore.api.rendering.SyRenderNodeComposite; +import com.io7m.jsycamore.api.rendering.SyRenderNodeNoop; +import com.io7m.jsycamore.api.rendering.SyRenderNodeShape; +import com.io7m.jsycamore.api.rendering.SyRenderNodeType; +import com.io7m.jsycamore.api.rendering.SyShapeRectangle; +import com.io7m.jsycamore.api.spaces.SySpaceComponentRelativeType; +import com.io7m.jsycamore.api.themes.SyThemeContextType; +import com.io7m.jsycamore.api.themes.SyThemeValueException; + +import java.util.Objects; +import java.util.Optional; + +import static com.io7m.jsycamore.theme.primal.internal.SyPrimalValues.BUTTON_PRESSED; +import static com.io7m.jsycamore.theme.primal.internal.SyPrimalValues.EMBOSS_EAST; +import static com.io7m.jsycamore.theme.primal.internal.SyPrimalValues.EMBOSS_NORTH; +import static com.io7m.jsycamore.theme.primal.internal.SyPrimalValues.EMBOSS_SOUTH; +import static com.io7m.jsycamore.theme.primal.internal.SyPrimalValues.EMBOSS_WEST; +import static com.io7m.jsycamore.theme.primal.internal.SyPrimalValues.PRIMARY_EDGE; + +/** + * A theme component for text areas. + */ + +public final class SyPrimalTextArea extends SyPrimalAbstract +{ + /** + * A theme component for text areas. + * + * @param inTheme The theme + */ + + public SyPrimalTextArea( + final SyThemePrimal inTheme) + { + super(inTheme); + } + + @Override + public SyRenderNodeType render( + final SyThemeContextType context, + final SyComponentReadableType component) + { + Objects.requireNonNull(context, "context"); + Objects.requireNonNull(component, "component"); + + if (component instanceof final SyTextAreaReadableType textArea) { + try { + final var area = + component.boundingArea(); + final var rectangle = + new SyShapeRectangle( + PAreasI.cast(PAreasI.moveToOrigin(area)) + ); + + final var rectArea = + rectangle.area(); + + final var values = + this.theme().values(); + + final var embossed = + SyEmbossedRectangle.emboss( + rectArea, + values.fillFlat(EMBOSS_SOUTH), + values.fillFlat(EMBOSS_WEST), + values.fillFlat(EMBOSS_NORTH), + values.fillFlat(EMBOSS_EAST), + 1, + 1 + ); + + final SyRenderNodeShape embossW = + new SyRenderNodeShape( + Optional.empty(), + Optional.of(embossed.fillWest()), + embossed.shapeWest() + ); + + final SyRenderNodeShape embossS = + new SyRenderNodeShape( + Optional.empty(), + Optional.of(embossed.fillSouth()), + embossed.shapeSouth() + ); + + final SyRenderNodeShape embossN = + new SyRenderNodeShape( + Optional.empty(), + Optional.of(embossed.fillNorth()), + embossed.shapeNorth() + ); + + final SyRenderNodeShape embossE = + new SyRenderNodeShape( + Optional.empty(), + Optional.of(embossed.fillEast()), + embossed.shapeEast() + ); + + final var mainFill = + new SyRenderNodeShape( + Optional.empty(), + Optional.of(values.fillFlat(BUTTON_PRESSED)), + rectangle + ); + + final var mainEdge = + new SyRenderNodeShape( + Optional.of(values.edgeFlat(PRIMARY_EDGE)), + Optional.empty(), + rectangle + ); + + return SyRenderNodeComposite.composite( + mainFill, embossN, embossE, embossS, embossW, mainEdge + ); + } catch (final SyThemeValueException e) { + throw new IllegalStateException(e); + } + } + + return SyRenderNodeNoop.noop(); + } +} diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextView.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextView.java index 8ac3de2b..5b369277 100644 --- a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextView.java +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextView.java @@ -58,7 +58,7 @@ public SyRenderNodeType render( final var area = component.boundingArea(); - if (component instanceof SyTextViewReadableType textView) { + if (component instanceof final SyTextViewReadableType textView) { final var theme = this.theme(); try { return new SyRenderNodeText( diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyThemePrimal.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyThemePrimal.java index 770aebe8..31c77cb8 100644 --- a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyThemePrimal.java +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyThemePrimal.java @@ -129,7 +129,6 @@ public SyThemePrimal() case MENU_ITEM -> { this.standards.put(className, new SyPrimalMenuItem(this)); } - case MENU_ITEM_ATOM -> { } @@ -139,17 +138,18 @@ public SyThemePrimal() case MENU_ITEM_SUBMENU -> { } - case MENU -> { this.standards.put(className, new SyPrimalMenu(this)); } + case TEXT_AREA -> { + this.standards.put(className, new SyPrimalTextArea(this)); + } case CHECKBOX, GRID_VIEW, LIST_VIEW, METER, SCROLLBAR, - TEXT_AREA, TEXT_FIELD -> { } diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/module-info.java b/com.io7m.jsycamore.theme.primal/src/main/java/module-info.java index 6c1d8c6d..8e0cab78 100644 --- a/com.io7m.jsycamore.theme.primal/src/main/java/module-info.java +++ b/com.io7m.jsycamore.theme.primal/src/main/java/module-info.java @@ -15,8 +15,6 @@ */ import com.io7m.jsycamore.api.text.SyFontServiceType; -import com.io7m.jsycamore.theme.primal.SyThemePrimalFactory; -import com.io7m.jsycamore.api.themes.SyThemeFactoryType; /** * {@code jsycamore} Primal theme.