Skip to content

Commit

Permalink
Make text areas use scroll panes
Browse files Browse the repository at this point in the history
Affects: #14
Affects: #13
  • Loading branch information
io7m committed Nov 26, 2023
1 parent fc61b5d commit 9017bd0
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public interface SyScrollPaneReadableType

SyComponentReadableType contentArea();

/**
* @return A readable reference to the internal content viewport
*/

SyComponentReadableType contentViewport();

/**
* @return A readable reference to the horizontal scroll bar
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,16 @@ default List<SyThemeClassNameType> themeClassesDefaultForComponent()
*/

AttributeReadableType<List<String>> textSections();

/**
* @return Access to the horizontal scrollbar
*/

SyScrollBarHorizontalReadableType scrollbarHorizontal();

/**
* @return Access to the vertical scrollbar
*/

SyScrollBarVerticalReadableType scrollbarVertical();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ public interface SyTextAreaType
*/

void textSectionAppend(String section);

@Override
SyScrollBarHorizontalType scrollbarHorizontal();

@Override
SyScrollBarVerticalType scrollbarVertical();
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,16 @@ public List<SyTextSectionLineType> textLayout(

final var indexNow =
breaker.getPosition();
final var bounds =
layout.getBounds();
final var brokenText =
text.substring(indexThen, indexNow);
final var textWidth =
this.textWidth(brokenText);

final var line =
new SectionLine(
PAreaSizeI.of((int) Math.ceil(bounds.getWidth()), this.textHeight()),
PAreaSizeI.of(textWidth, this.textHeight()),
Optional.of(layout),
text.substring(indexThen, indexNow)
brokenText
);

results.add(line);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@

import com.io7m.jattribute.core.AttributeReadableType;
import com.io7m.jattribute.core.AttributeType;
import com.io7m.jorchard.core.JOTreeNodeReadableType;
import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI;
import com.io7m.jsycamore.api.components.SyComponentReadableType;
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.components.SyTextAreaType;
import com.io7m.jsycamore.api.events.SyEventConsumed;
import com.io7m.jsycamore.api.events.SyEventType;
import com.io7m.jsycamore.api.layout.SyLayoutContextType;
import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType;
import com.io7m.jsycamore.api.themes.SyThemeClassNameType;
import com.io7m.jtensors.core.parameterized.vectors.PVector2I;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;

import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED;
import static java.lang.Math.min;

/**
* A text area.
Expand All @@ -42,12 +46,13 @@
public final class SyTextArea
extends SyComponentAbstract implements SyTextAreaType
{
private static final int INTERNAL_TEXT_PADDING = 2;
private static final int INTERNAL_TEXT_PADDING_DOUBLE =
INTERNAL_TEXT_PADDING + INTERNAL_TEXT_PADDING;
private static final int EDGE_PADDING = 8;
private static final int PADDING = EDGE_PADDING * 2;

private final AttributeType<List<String>> textSections;
private final SyPackVertical textContainer;
private final SyPackVertical textLayout;
private final SyScrollPaneType textScroller;
private final SyLayoutMargin textLayoutMargin;
private boolean viewsUpToDate;

/**
Expand All @@ -66,11 +71,22 @@ public SyTextArea(
this.textSections =
attributes.create(List.of());

this.textContainer = new SyPackVertical(inThemeClassesExtra);
this.textSections.subscribe(this::invalidateViews);
this.size().subscribe(this::invalidateViews);

this.childAdd(this.textContainer);
this.textScroller =
SyScrollPanes.create(inThemeClassesExtra);
this.textLayoutMargin =
new SyLayoutMargin();
this.textLayout =
new SyPackVertical();

this.textLayoutMargin.childAdd(this.textLayout);
this.textLayoutMargin.setPaddingAll(EDGE_PADDING);
this.textScroller.contentArea().childAdd(this.textLayoutMargin);
this.childAdd(this.textScroller);

this.textSections
.subscribe(this::invalidateViews);
this.size()
.subscribe(this::invalidateViews);
}

private void invalidateViews(
Expand Down Expand Up @@ -98,82 +114,95 @@ public PAreaSizeI<SySpaceParentRelativeType> layout(
final SyLayoutContextType layoutContext,
final SyConstraints constraints)
{
final var newSize =
super.layout(layoutContext, constraints);

/*
* Set this text area to the maximum allowed size.
* If the internal text views aren't up-to-date (perhaps because the
* text changed), then remove and create new views. This will yield a
* set of views from which we can determine the total required height
* of the content.
*/

final var sizeLimit =
this.sizeUpperLimit().get();
if (!this.viewsUpToDate) {
this.regenerateViews(layoutContext);
}

final var limitedConstraints =
new SyConstraints(
constraints.sizeMinimumX(),
constraints.sizeMinimumY(),
min(constraints.sizeMaximumX(), sizeLimit.sizeX()),
min(constraints.sizeMaximumY(), sizeLimit.sizeY())
);
final var textHeightRequired =
this.textViews()
.mapToInt(c -> c.size().get().sizeY())
.sum();

final var viewportSize =
this.textScroller.contentViewport()
.size()
.get()
.sizeX();

final PAreaSizeI<SySpaceParentRelativeType> newSize =
limitedConstraints.sizeMaximum();
this.textScroller.setContentAreaSize(
PAreaSizeI.of(viewportSize, textHeightRequired)
);

this.textScroller.layout(layoutContext, constraints);
return newSize;
}

this.setSize(newSize);
/**
* @return The width at which text should be wrapped
*/

private int textWrapWidth()
{
/*
* The text container is the current text area size, plus a margin.
* Wrap all text such that it is no wider than the viewport size, minus
* the padding applied to the text area.
*/

final var internalAreaPosition =
PVector2I.<SySpaceParentRelativeType>of(
INTERNAL_TEXT_PADDING,
INTERNAL_TEXT_PADDING
);
final var viewportSize =
this.textScroller.contentViewport()
.size()
.get()
.sizeX();

final var internalArea =
PAreaSizeI.<SySpaceParentRelativeType>of(
Math.max(0, newSize.sizeX() - INTERNAL_TEXT_PADDING_DOUBLE),
Math.max(0, newSize.sizeY() - INTERNAL_TEXT_PADDING_DOUBLE)
);
return Math.max(0, viewportSize - PADDING);
}

private Stream<SyComponentReadableType> textViews()
{
return this.textLayout
.nodeReadable()
.childrenReadable()
.stream()
.map(JOTreeNodeReadableType::value);
}

this.textContainer.position()
.set(internalAreaPosition);
this.textContainer.size()
.set(internalArea);
private void regenerateViews(
final SyLayoutContextType layoutContext)
{
this.textLayout.childrenClear();

/*
* If the internal text views aren't up-to-date (perhaps because the
* text changed), then remove and create new views, and then tell the
* text container to execute a layout.
*/
final var themeComponent =
layoutContext.themeCurrent()
.findForComponent(this);

if (!this.viewsUpToDate) {
this.textContainer.childrenClear();

final var themeComponent =
layoutContext.themeCurrent()
.findForComponent(this);

final var font =
themeComponent.font(layoutContext, this);

final var lines =
font.textLayoutMultiple(
this.textSections.get(),
internalArea.sizeX()
);

for (final var line : lines) {
final var textView = new SyTextView();
textView.setSize(line.size());
textView.setText(line.text());
textView.setMouseQueryAccepting(false);
this.textContainer.childAdd(textView);
}

this.textContainer.layout(layoutContext, limitedConstraints);
this.viewsUpToDate = true;
final var font =
themeComponent.font(layoutContext, this);

final var lines =
font.textLayoutMultiple(
this.textSections.get(),
this.textWrapWidth()
);

for (final var line : lines) {
final var textView = new SyTextView();
textView.setSize(line.size());
textView.setText(line.text());
textView.setMouseQueryAccepting(false);
this.textLayout.childAdd(textView);
}

return newSize;
this.viewsUpToDate = true;
}

@Override
Expand All @@ -184,4 +213,16 @@ public void textSectionAppend(
appended.add(section);
this.textSections.set(List.copyOf(appended));
}

@Override
public SyScrollBarHorizontalType scrollbarHorizontal()
{
return this.textScroller.scrollBarHorizontal();
}

@Override
public SyScrollBarVerticalType scrollbarVertical()
{
return this.textScroller.scrollBarVertical();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,22 @@ public PAreaSizeI<SySpaceParentRelativeType> layout(
final SyLayoutContextType layoutContext,
final SyConstraints constraints)
{
/*
* Note that text views are a special case when it comes to layout
* sizes. Text views must be dynamically sized according to their
* actual text content, and so cannot realistically be sized at the
* whim of whatever component is passing in constraints. They can, as
* a last resort, be hard clipped by setting a maximum size limit.
*/

final var requiredSize =
this.minimumSizeRequired(layoutContext);

final var limitSize =
this.sizeUpperLimit().get();

final var newSize =
constraints.<SySpaceParentRelativeType>sizeNotExceeding(
PAreaSizeI.<SySpaceParentRelativeType>of(
Math.min(requiredSize.sizeX(), limitSize.sizeX()),
Math.min(requiredSize.sizeY(), limitSize.sizeY())
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,21 +282,22 @@ public void setScrollAmountShown(
{
this.track.setScrollAmountShown(amount);

switch (this.presencePolicy.get()) {
case ALWAYS_ENABLED -> {
final var active = ACTIVE;
this.buttonLeft.setActive(active);
this.buttonRight.setActive(active);
this.track.setActive(active);
}
case DISABLED_IF_ENTIRE_RANGE_SHOWN -> {
final var active =
this.track.scrollAmountShown() >= 1.0 ? INACTIVE : ACTIVE;
this.buttonLeft.setActive(active);
this.buttonRight.setActive(active);
this.track.setActive(active);
}
}
final var all =
this.track.scrollAmountShown() >= 1.0;

final var active =
switch (this.presencePolicy.get()) {
case ALWAYS_ENABLED -> {
yield ACTIVE;
}
case DISABLED_IF_ENTIRE_RANGE_SHOWN -> {
yield all ? INACTIVE : ACTIVE;
}
};

this.buttonLeft.setActive(active);
this.buttonRight.setActive(active);
this.track.setActive(active);
}

@Override
Expand Down
Loading

0 comments on commit 9017bd0

Please sign in to comment.