/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.nui.layouts;
import org.terasology.math.geom.Rect2i;
import org.terasology.math.geom.Vector2i;
import org.terasology.rendering.nui.BaseInteractionListener;
import org.terasology.rendering.nui.Canvas;
import org.terasology.rendering.nui.CoreLayout;
import org.terasology.rendering.nui.InteractionListener;
import org.terasology.rendering.nui.LayoutConfig;
import org.terasology.rendering.nui.LayoutHint;
import org.terasology.rendering.nui.SubRegion;
import org.terasology.rendering.nui.UIWidget;
import org.terasology.rendering.nui.events.NUIMouseWheelEvent;
import org.terasology.rendering.nui.widgets.UIScrollbar;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Objects;
/**
*/
public class ScrollableArea extends CoreLayout {
private static final int SCROLL_MULTIPLIER = -42;
@LayoutConfig
private UIWidget content;
@LayoutConfig
private boolean stickToBottom;
@LayoutConfig
private boolean verticalScrollbar = true;
@LayoutConfig
private boolean horizontalScrollbar;
private UIScrollbar verticalBar = new UIScrollbar(true);
private UIScrollbar horizontalBar = new UIScrollbar(false);
private boolean moveToBottomPending;
private boolean moveToTopPending;
private InteractionListener scrollListener = new BaseInteractionListener() {
@Override
public boolean onMouseWheel(NUIMouseWheelEvent event) {
// If there are two scrollbars, we assume vertical has priority
if (verticalScrollbar) {
verticalBar.setValue(verticalBar.getValue() + event.getWheelTurns() * SCROLL_MULTIPLIER);
} else if (horizontalScrollbar) {
horizontalBar.setValue(horizontalBar.getValue() + event.getWheelTurns() * SCROLL_MULTIPLIER);
}
return true;
}
};
@Override
public void onDraw(Canvas canvas) {
int availableWidth = canvas.size().x;
int availableHeight = canvas.size().y;
// First, try to layout it without any scroll bars
Vector2i contentSize = canvas.calculateRestrictedSize(content, new Vector2i(availableWidth, availableHeight));
if (contentSize.x <= availableWidth && contentSize.y <= availableHeight) {
canvas.drawWidget(content, Rect2i.createFromMinAndSize(new Vector2i(0, 0), new Vector2i(availableWidth, availableHeight)));
return;
}
// Second, try to layout it just with vertical bar (if supported)
if (verticalScrollbar) {
int scrollbarWidth = canvas.calculateRestrictedSize(verticalBar, new Vector2i(availableWidth, availableHeight)).x;
int scrollbarHeight = canvas.calculateRestrictedSize(verticalBar, new Vector2i(availableWidth, availableHeight)).y;
contentSize = canvas.calculateRestrictedSize(content, new Vector2i(availableWidth - scrollbarWidth, Integer.MAX_VALUE));
if (horizontalScrollbar && contentSize.x > availableWidth - scrollbarWidth) {
if (contentSize.y > availableHeight - scrollbarHeight) {
layoutWithBothScrollbars(canvas, contentSize, availableWidth, availableHeight, scrollbarWidth, scrollbarHeight);
} else {
contentSize = canvas.calculateRestrictedSize(content, new Vector2i(availableWidth, availableHeight - scrollbarHeight));
layoutWithJustHorizontal(canvas, contentSize, availableWidth, availableHeight, scrollbarHeight);
}
} else {
layoutWithJustVertical(canvas, contentSize, availableWidth, availableHeight, scrollbarWidth);
}
} else if (horizontalScrollbar) {
// Well we know that just horizontal is allowed
int scrollbarHeight = canvas.calculateRestrictedSize(verticalBar, new Vector2i(availableWidth, availableHeight)).y;
availableHeight -= scrollbarHeight;
contentSize = canvas.calculateRestrictedSize(content, new Vector2i(Integer.MAX_VALUE, availableHeight - scrollbarHeight));
layoutWithJustHorizontal(canvas, contentSize, availableWidth, availableHeight, scrollbarHeight);
} else {
throw new IllegalStateException("ScrollableArea without any scrollbar allowed, what's the point of that?!");
}
}
private void layoutWithBothScrollbars(Canvas canvas, Vector2i contentSize, int fullWidth, int fullHeight, int scrollbarWidth, int scrollbarHeight) {
int availableWidth = fullWidth - scrollbarWidth;
int availableHeight = fullHeight - scrollbarHeight;
boolean atBottom = verticalBar.getRange() == verticalBar.getValue();
Rect2i contentRegion = Rect2i.createFromMinAndSize(0, 0, availableWidth, availableHeight);
verticalBar.setRange(contentSize.y - contentRegion.height());
horizontalBar.setRange(contentSize.x - contentRegion.width());
if ((stickToBottom && atBottom) || moveToBottomPending) {
verticalBar.setValue(verticalBar.getRange());
moveToBottomPending = false;
}
if (moveToTopPending) {
verticalBar.setValue(0);
moveToTopPending = false;
}
canvas.addInteractionRegion(scrollListener);
canvas.drawWidget(verticalBar, Rect2i.createFromMinAndSize(availableWidth, 0, scrollbarWidth, availableHeight));
canvas.drawWidget(horizontalBar, Rect2i.createFromMinAndSize(0, availableHeight, availableWidth, scrollbarHeight));
try (SubRegion ignored = canvas.subRegion(contentRegion, true)) {
canvas.drawWidget(content, Rect2i.createFromMinAndSize(-horizontalBar.getValue(), -verticalBar.getValue(), contentSize.x, contentSize.y));
}
}
private void layoutWithJustVertical(Canvas canvas, Vector2i contentSize, int fullWidth, int availableHeight, int scrollbarWidth) {
int availableWidth = fullWidth - scrollbarWidth;
boolean atBottom = verticalBar.getRange() == verticalBar.getValue();
Rect2i contentRegion = Rect2i.createFromMinAndSize(0, 0, availableWidth, availableHeight);
verticalBar.setRange(contentSize.y - contentRegion.height());
if ((stickToBottom && atBottom) || moveToBottomPending) {
verticalBar.setValue(verticalBar.getRange());
moveToBottomPending = false;
}
if (moveToTopPending) {
verticalBar.setValue(0);
moveToTopPending = false;
}
canvas.addInteractionRegion(scrollListener);
canvas.drawWidget(verticalBar, Rect2i.createFromMinAndSize(availableWidth, 0, scrollbarWidth, availableHeight));
try (SubRegion ignored = canvas.subRegion(contentRegion, true)) {
canvas.drawWidget(content, Rect2i.createFromMinAndSize(0, -verticalBar.getValue(), availableWidth, contentSize.y));
}
}
private void layoutWithJustHorizontal(Canvas canvas, Vector2i contentSize, int availableWidth, int fullHeight, int scrollbarHeight) {
int availableHeight = fullHeight - scrollbarHeight;
Rect2i contentRegion = Rect2i.createFromMinAndSize(0, 0, availableWidth, availableHeight);
horizontalBar.setRange(contentSize.x - contentRegion.width());
canvas.addInteractionRegion(scrollListener);
canvas.drawWidget(horizontalBar, Rect2i.createFromMinAndSize(0, availableHeight, availableWidth, scrollbarHeight));
try (SubRegion ignored = canvas.subRegion(contentRegion, true)) {
canvas.drawWidget(content, Rect2i.createFromMinAndSize(-horizontalBar.getValue(), 0, contentSize.x, availableHeight));
}
}
public void setContent(UIWidget widget) {
this.content = widget;
}
@Override
public Vector2i getPreferredContentSize(Canvas canvas, Vector2i sizeHint) {
return canvas.calculatePreferredSize(content);
}
@Override
public Vector2i getMaxContentSize(Canvas canvas) {
return new Vector2i(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
public Iterator<UIWidget> iterator() {
if (content != null) {
return Arrays.asList(content).iterator();
}
return Collections.emptyIterator();
}
@Override
public void addWidget(UIWidget element, LayoutHint hint) {
content = element;
}
@Override
public void removeWidget(UIWidget element) {
if (Objects.equals(element, content)) {
content = null;
}
}
public boolean isStickToBottom() {
return stickToBottom;
}
public void setStickToBottom(boolean stickToBottom) {
this.stickToBottom = stickToBottom;
}
public void moveToBottom() {
moveToBottomPending = true;
}
public void moveToTop() {
moveToTopPending = true;
}
}