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 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