diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyButtonType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyButtonType.java index a9b4a7ed..a01828d3 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyButtonType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyButtonType.java @@ -17,7 +17,10 @@ package com.io7m.jsycamore.api.components; /** - * The basic type of button components. + *

The basic type of button components.

+ *

A button is a component that notifies a listener when clicked.

+ *

The button has a "momentary" action; every time the mouse is clicked + * on the button, the listener is notified when the mouse button is released.

*/ public interface SyButtonType diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyCheckboxType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyCheckboxType.java index 46174a76..dd68fa5a 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyCheckboxType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyCheckboxType.java @@ -17,7 +17,11 @@ package com.io7m.jsycamore.api.components; /** - * The basic type of checkbox components. + *

The basic type of checkbox components.

+ *

A checkbox is similar to a button except that the checkbox is toggled + * by clicking it.

+ * + * @see SyButtonType */ public interface SyCheckboxType diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyConstraints.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyConstraints.java index b6c05a5c..19bc5199 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyConstraints.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyConstraints.java @@ -53,21 +53,13 @@ public SyConstraints( final int sizeMaximumY) { this.sizeMinimumX = - clamp(0, MAX_VALUE, sizeMinimumX); + Math.clamp(sizeMinimumX, 0, MAX_VALUE); this.sizeMaximumX = - clamp(this.sizeMinimumX, MAX_VALUE, sizeMaximumX); + Math.clamp(sizeMaximumX, this.sizeMinimumX, MAX_VALUE); this.sizeMinimumY = - clamp(0, MAX_VALUE, sizeMinimumY); + Math.clamp(sizeMinimumY, 0, MAX_VALUE); this.sizeMaximumY = - clamp(this.sizeMinimumY, MAX_VALUE, sizeMaximumY); - } - - private static int clamp( - final int min, - final int max, - final int v) - { - return Math.max(min, min(max, v)); + Math.clamp(sizeMaximumY, this.sizeMinimumY, MAX_VALUE); } /** @@ -108,8 +100,8 @@ public PAreaSizeI sizeWithin( final int sizeY) { return PAreaSizeI.of( - clamp(this.sizeMinimumX, this.sizeMaximumX, sizeX), - clamp(this.sizeMinimumY, this.sizeMaximumY, sizeY) + Math.clamp(sizeX, this.sizeMinimumX, this.sizeMaximumX), + Math.clamp(sizeY, this.sizeMinimumY, this.sizeMaximumY) ); } @@ -186,4 +178,66 @@ public SyConstraints deriveLimitedBy( min(this.sizeMaximumY(), size.sizeY()) ); } + + /** + * Derive a new set of constraints where the new maximum width has the given + * size subtracted from it. + * + * @param size The size to subtract + * + * @return A new set of constraints + */ + + public SyConstraints deriveSubtractMaximumWidth( + final int size) + { + return new SyConstraints( + this.sizeMinimumX, + this.sizeMinimumY, + this.sizeMaximumX - size, + this.sizeMaximumY + ); + } + + /** + * Derive a new set of constraints where the new maximum height has the given + * size subtracted from it. + * + * @param size The size to subtract + * + * @return A new set of constraints + */ + + public SyConstraints deriveSubtractMaximumHeight( + final int size) + { + return new SyConstraints( + this.sizeMinimumX, + this.sizeMinimumY, + this.sizeMaximumX, + this.sizeMaximumY - size + ); + } + + /** + * Derive a new set of constraints where the new maximum width and height + * values have the given sizes subtracted from them. + * + * @param sizeX The X size to subtract + * @param sizeY The Y size to subtract + * + * @return A new set of constraints + */ + + public SyConstraints deriveSubtractMaximumSizes( + final int sizeX, + final int sizeY) + { + return new SyConstraints( + this.sizeMinimumX, + this.sizeMinimumY, + this.sizeMaximumX - sizeX, + this.sizeMaximumY - sizeY + ); + } } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollBarReadableType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollBarReadableType.java index eb88d9c8..3dcd5a3c 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollBarReadableType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollBarReadableType.java @@ -49,6 +49,17 @@ public interface SyScrollBarReadableType double scrollPositionSnapping(); + /** + * Based on the current snapping setting and scrollbar size, determine a + * reasonable value to use to increment or decrement the current scroll + * position. This is used to implement up/down/left/right arrow buttons + * on scrollbars. + * + * @return The size of a single scroll increment in the range {@code [0, 1]} + */ + + double scrollIncrementSize(); + /** * @return The scrollbar presence policy */ diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollPaneReadableType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollPaneReadableType.java new file mode 100644 index 00000000..aead7698 --- /dev/null +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollPaneReadableType.java @@ -0,0 +1,68 @@ +/* + * 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.components; + +/** + * Read-only access to a scroll pane. + */ + +public interface SyScrollPaneReadableType + extends SyComponentReadableType +{ + /** + * @return A readable reference to the internal content region + */ + + SyComponentReadableType contentArea(); + + /** + * @return A readable reference to the horizontal scroll bar + */ + + SyScrollBarHorizontalReadableType scrollBarHorizontal(); + + /** + * @return A readable reference to the vertical scroll bar + */ + + SyScrollBarVerticalReadableType scrollBarVertical(); + + /** + * @return The default listener executed when clicking on a scrollbar button + */ + + Runnable onScrollClickLeftListener(); + + /** + * @return The default listener executed when clicking on a scrollbar button + */ + + Runnable onScrollClickRightListener(); + + /** + * @return The default listener executed when clicking on a scrollbar button + */ + + Runnable onScrollClickUpListener(); + + /** + * @return The default listener executed when clicking on a scrollbar button + */ + + Runnable onScrollClickDownListener(); +} diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollPaneType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollPaneType.java new file mode 100644 index 00000000..4d2787b9 --- /dev/null +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyScrollPaneType.java @@ -0,0 +1,49 @@ +/* + * 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.components; + +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; + +/** + *

The type of scroll panes.

+ *

A scroll pane provides a scrollable viewport into a much larger internal + * content region.

+ */ + +public interface SyScrollPaneType + extends SyComponentType, SyScrollPaneReadableType +{ + @Override + SyComponentType contentArea(); + + /** + * Set the fixed size of the internal content region. + * + * @param size The size + */ + + void setContentAreaSize( + PAreaSizeI size); + + @Override + SyScrollBarHorizontalType scrollBarHorizontal(); + + @Override + SyScrollBarVerticalType scrollBarVertical(); +} diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/themes/SyThemeClassNameStandard.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/themes/SyThemeClassNameStandard.java index dd0d8f93..16811d1f 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/themes/SyThemeClassNameStandard.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/themes/SyThemeClassNameStandard.java @@ -169,9 +169,6 @@ public enum SyThemeClassNameStandard SCROLLBAR_HORIZONTAL_BUTTON_THUMB_ICON("ScrollbarHorizontalButtonThumbIcon"), - - - /** * A horizontal scrollbar class. */ @@ -220,6 +217,24 @@ public enum SyThemeClassNameStandard SCROLLBAR_VERTICAL_BUTTON_THUMB_ICON("ScrollbarVerticalButtonThumbIcon"), + /** + * A scrollpane class. + */ + + SCROLLPANE("ScrollPane"), + + /** + * A scrollpane content area class. + */ + + SCROLLPANE_CONTENT_AREA("ScrollPaneContentArea"), + + /** + * A scrollpane content area viewport class. + */ + + SCROLLPANE_CONTENT_AREA_VIEWPORT("ScrollPaneContentAreaViewport"), + /** * A text area class. */ diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyComponentAbstract.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyComponentAbstract.java index 7d390cd4..2399b545 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyComponentAbstract.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyComponentAbstract.java @@ -364,10 +364,19 @@ public final JOTreeNodeType node() @Override public final String toString() { + final var currentSize = + this.size.get(); + final var currentPosition = + this.position.get(); + return String.format( - "[%s 0x%s]", + "[%s 0x%s %dx%d %d,%d]", this.getClass().getSimpleName(), - Integer.toUnsignedString(this.hashCode(), 16) + Integer.toUnsignedString(this.hashCode(), 16), + Integer.valueOf(currentSize.sizeX()), + Integer.valueOf(currentSize.sizeY()), + Integer.valueOf(currentPosition.x()), + Integer.valueOf(currentPosition.y()) ); } } diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyScrollPanes.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyScrollPanes.java new file mode 100644 index 00000000..0b22d037 --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/SyScrollPanes.java @@ -0,0 +1,61 @@ +/* + * 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.jsycamore.api.components.SyScrollPaneType; +import com.io7m.jsycamore.api.themes.SyThemeClassNameType; +import com.io7m.jsycamore.components.standard.internal.scrollpanes.SyScrollPane; + +import java.util.List; + +/** + * Functions to create scroll panes. + */ + +public final class SyScrollPanes +{ + private SyScrollPanes() + { + + } + + /** + * Create a scroll pane. + * + * @param themeClassesExtra The extra theme classes + * + * @return A scroll pane + */ + + public static SyScrollPaneType create( + final List themeClassesExtra) + { + return new SyScrollPane(themeClassesExtra); + } + + /** + * Create a scroll pane. + * + * @return A scroll pane + */ + + public static SyScrollPaneType create() + { + return create(List.of()); + } +} diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarH.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarH.java index 17500247..ffd258b8 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarH.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarH.java @@ -256,6 +256,12 @@ public void removeOnThumbDragListener() this.track.removeOnThumbDragListener(); } + @Override + public double scrollIncrementSize() + { + return this.track.scrollIncrementSize(); + } + @Override public void setScrollPosition( final double position) diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonLeft.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonLeft.java index 758ae3df..eac0932e 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonLeft.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonLeft.java @@ -41,6 +41,7 @@ final class SyScrollBarHButtonLeft extends SyButtonAbstract this.image = new SyImageView(List.of(SCROLLBAR_HORIZONTAL_BUTTON_LEFT_ICON)); this.image.setImageURI("jsycamore:icon:scroll_left"); + this.image.setMouseQueryAccepting(false); this.align = new SyAlign(); this.align.alignmentHorizontal().set(ALIGN_HORIZONTAL_CENTER); diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonRight.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonRight.java index 75c502cd..a372b8a1 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonRight.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonRight.java @@ -42,6 +42,7 @@ final class SyScrollBarHButtonRight extends SyButtonAbstract this.image = new SyImageView(List.of( SCROLLBAR_HORIZONTAL_BUTTON_RIGHT_ICON)); this.image.setImageURI("jsycamore:icon:scroll_right"); + this.image.setMouseQueryAccepting(false); this.align = new SyAlign(); this.align.alignmentHorizontal().set(ALIGN_HORIZONTAL_CENTER); diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonThumb.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonThumb.java index 42ae3a95..4940d861 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonThumb.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHButtonThumb.java @@ -75,6 +75,7 @@ final class SyScrollBarHButtonThumb this.image = new SyImageView(List.of(SCROLLBAR_HORIZONTAL_BUTTON_THUMB_ICON)); this.image.setImageURI("jsycamore:icon:scroll_h_thumb"); + this.image.setMouseQueryAccepting(false); this.align = new SyAlign(); this.align.alignmentHorizontal().set(ALIGN_HORIZONTAL_CENTER); diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHTrack.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHTrack.java index 5eb39001..3c81e0a6 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHTrack.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarHTrack.java @@ -159,4 +159,13 @@ SyComponentReadableType thumb() { return this.scrollAmount; } + + public double scrollIncrementSize() + { + final var width = + (double) this.size().get().sizeX(); + final var base = + 1.0 / width; + return snapDouble(base, this.scrollPositionSnap); + } } diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarV.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarV.java index adb279e2..0705d1ed 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarV.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarV.java @@ -303,6 +303,12 @@ public double scrollPositionSnapping() return this.track.scrollPositionSnapping(); } + @Override + public double scrollIncrementSize() + { + return this.track.scrollIncrementSize(); + } + @Override public AttributeType presencePolicy() { diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonDown.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonDown.java index f227e1e5..5e2346c4 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonDown.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonDown.java @@ -41,6 +41,7 @@ final class SyScrollBarVButtonDown extends SyButtonAbstract this.image = new SyImageView(List.of(SCROLLBAR_VERTICAL_BUTTON_DOWN_ICON)); this.image.setImageURI("jsycamore:icon:scroll_down"); + this.image.setMouseQueryAccepting(false); this.align = new SyAlign(); this.align.alignmentHorizontal().set(ALIGN_HORIZONTAL_CENTER); diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonThumb.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonThumb.java index eb03111a..bd8635ef 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonThumb.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonThumb.java @@ -75,6 +75,7 @@ final class SyScrollBarVButtonThumb this.image = new SyImageView(List.of(SCROLLBAR_VERTICAL_BUTTON_THUMB_ICON)); this.image.setImageURI("jsycamore:icon:scroll_v_thumb"); + this.image.setMouseQueryAccepting(false); this.align = new SyAlign(); this.align.alignmentHorizontal().set(ALIGN_HORIZONTAL_CENTER); diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonUp.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonUp.java index 8acd1732..b5f87999 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonUp.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVButtonUp.java @@ -41,6 +41,7 @@ final class SyScrollBarVButtonUp extends SyButtonAbstract this.image = new SyImageView(List.of(SCROLLBAR_VERTICAL_BUTTON_UP_ICON)); this.image.setImageURI("jsycamore:icon:scroll_up"); + this.image.setMouseQueryAccepting(false); this.align = new SyAlign(); this.align.alignmentHorizontal().set(ALIGN_HORIZONTAL_CENTER); diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVTrack.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVTrack.java index 23255dc1..dc76c282 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVTrack.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollbars/SyScrollBarVTrack.java @@ -159,4 +159,13 @@ SyComponentReadableType thumb() { return this.scrollAmount; } + + public double scrollIncrementSize() + { + final var height = + (double) this.size().get().sizeY(); + final var base = + 1.0 / height; + return snapDouble(base, this.scrollPositionSnap); + } } diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPane.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPane.java new file mode 100644 index 00000000..fd574977 --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPane.java @@ -0,0 +1,328 @@ +/* + * 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.internal.scrollpanes; + +import com.io7m.jattribute.core.AttributeType; +import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.components.SyComponentType; +import com.io7m.jsycamore.api.components.SyConstraints; +import com.io7m.jsycamore.api.components.SyScrollBarHorizontalType; +import com.io7m.jsycamore.api.components.SyScrollBarVerticalType; +import com.io7m.jsycamore.api.components.SyScrollPaneType; +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.jsycamore.components.standard.SyComponentAbstract; +import com.io7m.jsycamore.components.standard.SyComponentAttributes; +import com.io7m.jsycamore.components.standard.SyScrollBarsHorizontal; +import com.io7m.jsycamore.components.standard.SyScrollBarsVertical; +import com.io7m.jtensors.core.parameterized.vectors.PVector2I; +import com.io7m.jtensors.core.parameterized.vectors.PVectors2I; + +import java.util.List; + +import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED; +import static com.io7m.jsycamore.api.themes.SyThemeClassNameStandard.SCROLLPANE; + +/** + * The main scroll pane implementation. + */ + +public final class SyScrollPane + extends SyComponentAbstract implements SyScrollPaneType +{ + private final SyScrollBarHorizontalType scrollH; + private final SyScrollBarVerticalType scrollV; + private final SyScrollPaneContentArea contentArea; + private final SyScrollPaneContentAreaViewport contentAreaViewport; + private final AttributeType> contentAreaSize; + + /** + * The main scroll pane implementation. + * + * @param inExtraClasses The extra classes + */ + + public SyScrollPane( + final List inExtraClasses) + { + super(inExtraClasses); + + this.scrollH = + SyScrollBarsHorizontal.create(); + this.scrollV = + SyScrollBarsVertical.create(); + this.contentArea = + new SyScrollPaneContentArea(); + this.contentAreaViewport = + new SyScrollPaneContentAreaViewport(); + + final var attributes = + SyComponentAttributes.get(); + + this.contentAreaSize = + attributes.create(PAreaSizeI.of(1024, 1024)); + + this.childAdd(this.contentAreaViewport); + this.childAdd(this.scrollH); + this.childAdd(this.scrollV); + + this.scrollH.setOnClickLeftListener( + this.onScrollClickLeftListener()); + this.scrollH.setOnClickRightListener( + this.onScrollClickRightListener()); + this.scrollV.setOnClickUpListener( + this.onScrollClickUpListener()); + this.scrollV.setOnClickDownListener( + this.onScrollClickDownListener()); + } + + private void updateScrollBars( + final PAreaSizeI newContentAreaSize, + final PAreaSizeI newContentViewportSize) + { + final var viewportX = + (double) newContentViewportSize.sizeX(); + final var viewportY = + (double) newContentViewportSize.sizeY(); + + final var contentX = + (double) newContentAreaSize.sizeX(); + final var contentY = + (double) newContentAreaSize.sizeY(); + + /* + * Clamp the results to [0, 1]. This has the effect of stamping out any + * NaN values if the content area size is zero. + */ + + final var amountX = + Math.clamp(viewportX / contentX, 0.0, 1.0); + final var amountY = + Math.clamp(viewportY / contentY, 0.0, 1.0); + + this.scrollH.setScrollAmountShown(amountX); + this.scrollV.setScrollAmountShown(amountY); + } + + @Override + public PAreaSizeI layout( + final SyLayoutContextType layoutContext, + final SyConstraints constraints) + { + /* + * Resize this main component to the maximum size allowed by the + * constraints. + */ + + var limitedConstraints = + layoutContext.deriveThemeConstraints(constraints, this); + + final var sizeLimit = + this.sizeUpperLimit().get(); + + limitedConstraints = + limitedConstraints.deriveLimitedBy(sizeLimit); + + final PAreaSizeI newSize = + limitedConstraints.sizeMaximum(); + + this.setSize(newSize); + + /* + * Perform an initial layout pass for both scrollbars. This gets them + * configured to their initial vaguely sensible sizes. + */ + + this.scrollH.layout(layoutContext, limitedConstraints); + this.scrollV.layout(layoutContext, limitedConstraints); + + /* + * Now, the scrollbars needed to be shortened slightly, and placed at + * the edges of the component. + */ + + final var scrollHConstraints = + limitedConstraints.deriveSubtractMaximumWidth( + this.scrollV.size().get().sizeX() + ); + + final var scrollVConstraints = + limitedConstraints.deriveSubtractMaximumHeight( + this.scrollH.size().get().sizeY() + ); + + this.scrollH.layout(layoutContext, scrollHConstraints); + this.scrollV.layout(layoutContext, scrollVConstraints); + + /* + * Laying out the content area is slightly more complex. The size of the + * content area viewport is derived from the size of the scrollpane as a + * whole, minus the sizes of the scrollbars. The size of the content area + * within the viewport is of a fixed size and may be far, far larger than + * the content viewport. + * + * Therefore, to set their sizes independently: + * + * 1. Detach the content area from the viewport. + * 2. Lay out the viewport. + * 3. Lay out the content area. + * 4. Re-attach the content area to the viewport. + * 5. Position the content area so that the correct area is seen through + * the viewport. + */ + + final var contentViewportConstraints = + limitedConstraints.deriveSubtractMaximumSizes( + this.scrollV.size().get().sizeX(), + this.scrollH.size().get().sizeY() + ); + + final var contentAreaSizeNow = + this.contentAreaSize.get(); + + final var contentAreaConstraints = + new SyConstraints( + 0, + 0, + contentAreaSizeNow.sizeX(), + contentAreaSizeNow.sizeY() + ); + + this.contentAreaViewport.childRemove(this.contentArea); + this.contentArea.layout(layoutContext, contentAreaConstraints); + + this.contentAreaViewport.layout(layoutContext, contentViewportConstraints); + this.contentAreaViewport.childAdd(this.contentArea); + this.contentAreaViewport.setPosition(PVectors2I.zero()); + + final var contentViewportSizeNow = + this.contentAreaViewport.size().get(); + + this.scrollH.setPosition( + PVector2I.of(0, contentViewportSizeNow.sizeY()) + ); + this.scrollV.setPosition( + PVector2I.of(contentViewportSizeNow.sizeX(), 0) + ); + + this.updateScrollBars(contentAreaSizeNow, contentViewportSizeNow); + + /* + * The content area is offset by a proportion of the scrollable region + * within the viewport. + */ + + final var scrollRegionX = + Math.max(0, contentAreaSizeNow.sizeX() - contentViewportSizeNow.sizeX()); + final var scrollRegionY = + Math.max(0, contentAreaSizeNow.sizeY() - contentViewportSizeNow.sizeY()); + + final var contentAreaX = + (int) -(this.scrollH.scrollPosition() * scrollRegionX); + final var contentAreaY = + (int) -(this.scrollV.scrollPosition() * scrollRegionY); + + this.contentArea.setPosition(PVector2I.of(contentAreaX, contentAreaY)); + return newSize; + } + + @Override + protected SyEventConsumed onEvent( + final SyEventType event) + { + return EVENT_NOT_CONSUMED; + } + + @Override + public List themeClassesDefaultForComponent() + { + return List.of(SCROLLPANE); + } + + @Override + public SyComponentType contentArea() + { + return this.contentArea; + } + + @Override + public void setContentAreaSize( + final PAreaSizeI size) + { + this.contentAreaSize.set(size); + } + + @Override + public SyScrollBarHorizontalType scrollBarHorizontal() + { + return this.scrollH; + } + + @Override + public SyScrollBarVerticalType scrollBarVertical() + { + return this.scrollV; + } + + @Override + public Runnable onScrollClickLeftListener() + { + return this::doScrollClickLeft; + } + + @Override + public Runnable onScrollClickRightListener() + { + return this::doScrollClickRight; + } + + @Override + public Runnable onScrollClickUpListener() + { + return this::doScrollClickUp; + } + + @Override + public Runnable onScrollClickDownListener() + { + return this::doScrollClickDown; + } + + private void doScrollClickLeft() + { + this.scrollH.setScrollPosition(this.scrollH.scrollPosition() - this.scrollH.scrollIncrementSize()); + } + + private void doScrollClickRight() + { + this.scrollH.setScrollPosition(this.scrollH.scrollPosition() + this.scrollH.scrollIncrementSize()); + } + + private void doScrollClickUp() + { + this.scrollV.setScrollPosition(this.scrollV.scrollPosition() - this.scrollV.scrollIncrementSize()); + } + + private void doScrollClickDown() + { + this.scrollV.setScrollPosition(this.scrollV.scrollPosition() + this.scrollV.scrollIncrementSize()); + } +} diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPaneContentArea.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPaneContentArea.java new file mode 100644 index 00000000..8446a71e --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPaneContentArea.java @@ -0,0 +1,49 @@ +/* + * 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.internal.scrollpanes; + +import com.io7m.jsycamore.api.events.SyEventConsumed; +import com.io7m.jsycamore.api.events.SyEventType; +import com.io7m.jsycamore.api.themes.SyThemeClassNameType; +import com.io7m.jsycamore.components.standard.SyComponentAbstract; + +import java.util.List; + +import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED; +import static com.io7m.jsycamore.api.themes.SyThemeClassNameStandard.SCROLLPANE_CONTENT_AREA; + +final class SyScrollPaneContentArea extends SyComponentAbstract +{ + SyScrollPaneContentArea() + { + super(List.of()); + } + + @Override + public List themeClassesDefaultForComponent() + { + return List.of(SCROLLPANE_CONTENT_AREA); + } + + @Override + protected SyEventConsumed onEvent( + final SyEventType event) + { + return EVENT_NOT_CONSUMED; + } +} diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPaneContentAreaViewport.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPaneContentAreaViewport.java new file mode 100644 index 00000000..beeee00f --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/SyScrollPaneContentAreaViewport.java @@ -0,0 +1,49 @@ +/* + * 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.internal.scrollpanes; + +import com.io7m.jsycamore.api.events.SyEventConsumed; +import com.io7m.jsycamore.api.events.SyEventType; +import com.io7m.jsycamore.api.themes.SyThemeClassNameType; +import com.io7m.jsycamore.components.standard.SyComponentAbstract; + +import java.util.List; + +import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED; +import static com.io7m.jsycamore.api.themes.SyThemeClassNameStandard.SCROLLPANE_CONTENT_AREA_VIEWPORT; + +final class SyScrollPaneContentAreaViewport extends SyComponentAbstract +{ + SyScrollPaneContentAreaViewport() + { + super(List.of()); + } + + @Override + public List themeClassesDefaultForComponent() + { + return List.of(SCROLLPANE_CONTENT_AREA_VIEWPORT); + } + + @Override + protected SyEventConsumed onEvent( + final SyEventType event) + { + return EVENT_NOT_CONSUMED; + } +} diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/package-info.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/package-info.java new file mode 100644 index 00000000..9bcc8fbc --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/internal/scrollpanes/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Embedded GUI library (Standard components [Internals]) + */ + +@Version("1.0.0") +package com.io7m.jsycamore.components.standard.internal.scrollpanes; + +import org.osgi.annotation.versioning.Version; diff --git a/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyScrollPaneDemo.java b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyScrollPaneDemo.java new file mode 100644 index 00000000..f88609c4 --- /dev/null +++ b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyScrollPaneDemo.java @@ -0,0 +1,281 @@ +/* + * 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.components.SyScrollPaneType; +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.SyButton; +import com.io7m.jsycamore.components.standard.SyLayoutMargin; +import com.io7m.jsycamore.components.standard.SyLayoutVertical; +import com.io7m.jsycamore.components.standard.SyPackHorizontal; +import com.io7m.jsycamore.components.standard.SyPackVertical; +import com.io7m.jsycamore.components.standard.SyScrollPanes; +import com.io7m.jsycamore.components.standard.forms.SyForm; +import com.io7m.jsycamore.components.standard.forms.SyFormColumnSizeType; +import com.io7m.jsycamore.components.standard.forms.SyFormColumnsConfiguration; +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.concurrent.Executors; + +import static com.io7m.jsycamore.api.windows.SyWindowCloseBehaviour.HIDE_ON_CLOSE_BUTTON; +import static java.lang.Boolean.TRUE; +import static java.util.concurrent.TimeUnit.SECONDS; + +public final class SyScrollPaneDemo +{ + private static final Logger LOG = + LoggerFactory.getLogger(SyScrollPaneDemo.class); + + private SyScrollPaneDemo() + { + 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 SyScrollPaneType scrollPane; + + 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) + { + SyScrollPaneDemo.Canvas.this.screen.mouseDown( + PVector2I.of(e.getX(), e.getY()), + SyMouseButton.ofIndex(e.getButton() - 1)); + SyScrollPaneDemo.Canvas.this.repaint(); + } + + @Override + public void mouseDragged( + final MouseEvent e) + { + SyScrollPaneDemo.Canvas.this.screen.mouseMoved(PVector2I.of( + e.getX(), + e.getY())); + SyScrollPaneDemo.Canvas.this.repaint(); + } + + @Override + public void mouseReleased( + final MouseEvent e) + { + SyScrollPaneDemo.Canvas.this.screen.mouseUp( + PVector2I.of(e.getX(), e.getY()), + SyMouseButton.ofIndex(e.getButton() - 1)); + SyScrollPaneDemo.Canvas.this.repaint(); + } + + @Override + public void mouseMoved( + final MouseEvent e) + { + final PVector2I position = + PVector2I.of(e.getX(), e.getY()); + SyScrollPaneDemo.Canvas.this.screen.mouseMoved(position); + SyScrollPaneDemo.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) + { + SyScrollPaneDemo.Canvas.this.screen.setSize( + PAreaSizeI.of( + e.getComponent().getWidth(), + e.getComponent().getHeight()) + ); + } + }); + + { + final var margin = new SyLayoutMargin(); + margin.setPaddingAll(8); + + final var paneContent = new SyLayoutVertical(); + for (int index = 0; index < 10; ++index) { + paneContent.childAdd(new SyButton("Button " + index)); + } + + this.scrollPane = SyScrollPanes.create(); + this.scrollPane.contentArea().childAdd(paneContent); + this.scrollPane.setContentAreaSize(PAreaSizeI.of(800, 800)); + margin.childAdd(this.scrollPane); + + this.window0.decorated().set(TRUE); + this.window0.contentArea().childAdd(margin); + this.window0.title().set("Window Title"); + this.window0.positionSnapping().set(Integer.valueOf(16)); + this.window0.sizeSnapping().set(Integer.valueOf(16)); + } + + 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.nodeRenderer().setDebugBoundsRendering(true); + this.renderer.render(g2, this.screen, window); + } + } + } +} \ No newline at end of file diff --git a/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyScrollPaneTest.java b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyScrollPaneTest.java new file mode 100644 index 00000000..571e7b40 --- /dev/null +++ b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyScrollPaneTest.java @@ -0,0 +1,46 @@ +/* + * 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.jregions.core.parameterized.sizes.PAreaSizeI; +import com.io7m.jsycamore.api.components.SyScrollPaneType; +import com.io7m.jsycamore.components.standard.SyScrollPanes; +import com.io7m.jtensors.core.parameterized.vectors.PVectors2I; +import org.junit.jupiter.api.Test; + +public final class SyScrollPaneTest extends SyComponentContract +{ + @Override + protected SyScrollPaneType newComponent() + { + return SyScrollPanes.create(); + } + + @Test + public void testTooSmall() + { + final var c = this.newComponent(); + c.setSizeUpperLimit(PAreaSizeI.of(0, 0)); + c.setContentAreaSize(PAreaSizeI.of(0, 0)); + + this.windowContentArea().childAdd(c); + this.window().layout(this.layoutContext); + this.screen().mouseMoved(PVectors2I.zero()); + + c.position(); + } +} diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPane.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPane.java new file mode 100644 index 00000000..2c9d796d --- /dev/null +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPane.java @@ -0,0 +1,54 @@ +/* + * 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.jsycamore.api.components.SyComponentReadableType; +import com.io7m.jsycamore.api.rendering.SyRenderNodeNoop; +import com.io7m.jsycamore.api.rendering.SyRenderNodeType; +import com.io7m.jsycamore.api.themes.SyThemeContextType; + +import java.util.Objects; + +/** + * A theme component for scroll panes. + */ + +public final class SyPrimalScrollPane extends SyPrimalAbstract +{ + /** + * A theme component for scroll panes. + * + * @param inTheme The theme + */ + + public SyPrimalScrollPane( + final SyThemePrimal inTheme) + { + super(inTheme); + } + + @Override + public SyRenderNodeType render( + final SyThemeContextType context, + final SyComponentReadableType component) + { + Objects.requireNonNull(context, "context"); + Objects.requireNonNull(component, "component"); + + return SyRenderNodeNoop.noop(); + } +} diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPaneContentArea.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPaneContentArea.java new file mode 100644 index 00000000..0a98b637 --- /dev/null +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPaneContentArea.java @@ -0,0 +1,54 @@ +/* + * 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.jsycamore.api.components.SyComponentReadableType; +import com.io7m.jsycamore.api.rendering.SyRenderNodeNoop; +import com.io7m.jsycamore.api.rendering.SyRenderNodeType; +import com.io7m.jsycamore.api.themes.SyThemeContextType; + +import java.util.Objects; + +/** + * A theme component for scroll pane content areas. + */ + +public final class SyPrimalScrollPaneContentArea extends SyPrimalAbstract +{ + /** + * A theme component for scroll pane content areas. + * + * @param inTheme The theme + */ + + public SyPrimalScrollPaneContentArea( + final SyThemePrimal inTheme) + { + super(inTheme); + } + + @Override + public SyRenderNodeType render( + final SyThemeContextType context, + final SyComponentReadableType component) + { + Objects.requireNonNull(context, "context"); + Objects.requireNonNull(component, "component"); + + return SyRenderNodeNoop.noop(); + } +} diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPaneContentAreaViewport.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPaneContentAreaViewport.java new file mode 100644 index 00000000..a47070ae --- /dev/null +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalScrollPaneContentAreaViewport.java @@ -0,0 +1,78 @@ +/* + * 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.PAreaI; +import com.io7m.jsycamore.api.components.SyComponentReadableType; +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.PRIMARY_OVER; + +/** + * A theme component for scroll pane content area viewports. + */ + +public final class SyPrimalScrollPaneContentAreaViewport extends SyPrimalAbstract +{ + /** + * A theme component for scroll pane content area viewports. + * + * @param inTheme The theme + */ + + public SyPrimalScrollPaneContentAreaViewport( + final SyThemePrimal inTheme) + { + super(inTheme); + } + + @Override + public SyRenderNodeType render( + final SyThemeContextType context, + final SyComponentReadableType component) + { + Objects.requireNonNull(context, "context"); + Objects.requireNonNull(component, "component"); + + final var area = + component.boundingArea(); + + final var rectangle = + new SyShapeRectangle( + PAreaI.of(0, area.sizeX(), 0, area.sizeY()) + ); + + try { + final var values = this.theme().values(); + return new SyRenderNodeShape( + Optional.of(values.edgeFlat(PRIMARY_OVER)), + Optional.empty(), + rectangle + ); + } catch (final SyThemeValueException e) { + throw new IllegalStateException(e); + } + } +} 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 777f5906..3c503f18 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 @@ -198,6 +198,16 @@ public SyThemePrimal() this.standards.put(className, new SyPrimalScrollbarVButtonIcon(this, imageView)); } + case SCROLLPANE -> { + this.standards.put(className, new SyPrimalScrollPane(this)); + } + case SCROLLPANE_CONTENT_AREA -> { + this.standards.put(className, new SyPrimalScrollPaneContentArea(this)); + } + case SCROLLPANE_CONTENT_AREA_VIEWPORT -> { + this.standards.put(className, new SyPrimalScrollPaneContentAreaViewport(this)); + } + case TEXT_AREA -> { this.standards.put(className, new SyPrimalTextArea(this)); } diff --git a/com.io7m.jsycamore.theme.primal/src/main/resources/com/io7m/jsycamore/theme/primal/scroll_down.xcf b/com.io7m.jsycamore.theme.primal/src/main/resources/com/io7m/jsycamore/theme/primal/scroll_down.xcf index 1dd7506a..4ba40d16 100644 Binary files a/com.io7m.jsycamore.theme.primal/src/main/resources/com/io7m/jsycamore/theme/primal/scroll_down.xcf and b/com.io7m.jsycamore.theme.primal/src/main/resources/com/io7m/jsycamore/theme/primal/scroll_down.xcf differ