/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.client.gui.controller.game;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.tools.Color;
import de.lessvoid.nifty.tools.SizeValue;
import illarion.client.gui.Tooltip;
import illarion.client.gui.events.TooltipsRemovedEvent;
import illarion.client.world.World;
import illarion.common.types.Rectangle;
import org.bushe.swing.event.EventBus;
import org.illarion.engine.GameContainer;
import org.illarion.engine.input.Input;
import org.illarion.nifty.controls.tooltip.builder.ToolTipBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This tooltip handler takes care of showing and hiding the item tooltips on the screen.
*
* @author Martin Karing <nitram@illarion.org>
*/
public final class TooltipHandler implements ScreenController, UpdatableHandler {
@Nonnull
private static final Logger log = LoggerFactory.getLogger(TooltipHandler.class);
/**
* The Nifty that is the parent to this handler.
*/
private Nifty parentNifty;
/**
* The screen that is the parent to this handler.
*/
private Screen parentScreen;
/**
* The layer-element that contains all the tooltips.
*/
@Nullable
private Element toolTipLayer;
/**
* The area the mouse as the remain inside to keep the tooltip active.
*/
@Nullable
private Rectangle activeTooltipArea;
/**
* The last reported x coordinate of the mouse cursor.
*/
private int lastMouseX;
/**
* The last reported y coordinate of the mouse cursor.
*/
private int lastMouseY;
@Override
public void bind(@Nonnull Nifty nifty, @Nonnull Screen screen) {
parentNifty = nifty;
parentScreen = screen;
toolTipLayer = screen.findElementById("tooltipLayer");
}
@Override
public void onStartScreen() {
// nothing
}
@Override
public void onEndScreen() {
hideToolTipImpl();
}
@Override
public void update(@Nonnull GameContainer container, int delta) {
Input input = container.getEngine().getInput();
lastMouseX = input.getMouseX();
lastMouseY = input.getMouseY();
if (activeTooltipArea != null) {
if (!activeTooltipArea.isInside(lastMouseX, lastMouseY)) {
log.debug("Removing active tooltip. Mouse (x:{} y:{}) is outside of {}", lastMouseX, lastMouseY,
activeTooltipArea);
hideToolTip();
EventBus.publish(new TooltipsRemovedEvent());
}
}
}
/**
* Hide all current tooltips.
*/
public void hideToolTip() {
World.getUpdateTaskManager().addTask((container, delta) -> hideToolTipImpl());
}
private void hideToolTipImpl() {
if (toolTipLayer == null) {
return;
}
for (Element element : toolTipLayer.getChildren()) {
element.hide(element::markForRemoval);
activeTooltipArea = null;
}
}
/**
* Create a new tooltip.
*
* @param location the tooltip should be place around, the area of this rectangle won't be hidden by the tooltip.
* Also the mouse is required to remain inside this area to keep the tooltip active
* @param tooltip the tooltip to display
*/
public void showToolTip(@Nonnull Rectangle location, @Nonnull Tooltip tooltip) {
if (!tooltip.isValid()) {
log.warn("Received a invalid tooltip from the server!");
return;
}
if (!location.isInside(lastMouseX, lastMouseY)) {
return;
}
log.debug("Showing tooltip {} for {}", tooltip, location);
World.getUpdateTaskManager().addTask((container, delta) -> {
hideToolTipImpl();
showToolTipImpl(location, tooltip);
activeTooltipArea = location;
});
}
public boolean isTooltipActive() {
return !toolTipLayer.getChildren().isEmpty();
}
private long count = Long.MIN_VALUE;
/**
* Create a new tooltip. This is the internal implementation that is only called from the update loop.
*
* @param location the tooltip should be place around, the area of this rectangle won't be hidden by the tooltip
* @param tooltip the tooltip to display
*/
private void showToolTipImpl(@Nonnull Rectangle location, @Nonnull Tooltip tooltip) {
ToolTipBuilder builder = new ToolTipBuilder("tooltip-" + Long.toString(count++));
builder.title(tooltip.getName());
switch (tooltip.getRareness()) {
case Tooltip.RARENESS_UNCOMMON:
builder.titleColor("#00a500ff");
break;
case Tooltip.RARENESS_RARE:
builder.titleColor("#003fbfff");
break;
case Tooltip.RARENESS_EPIC:
builder.titleColor("#bb00ffff");
break;
case Tooltip.RARENESS_COMMON:
default:
builder.titleColor(Color.WHITE);
break;
}
builder.description(tooltip.getDescription());
builder.producer(tooltip.getCraftedBy());
builder.type(tooltip.getType());
builder.level(tooltip.getLevel());
if (tooltip.isUsable()) {
builder.levelColor(Color.WHITE);
} else {
builder.levelColor("#ff0000ff");
}
builder.worth(tooltip.getWorth().getTotalCopper() / 20);
if (tooltip.getWeight() > 0) {
builder.weight(Integer.toString(tooltip.getWeight()));
}
builder.quality(tooltip.getQualityText());
builder.durability(tooltip.getDurabilityText());
builder.amethystLevel(tooltip.getAmethystLevel());
builder.obsidianLevel(tooltip.getObsidianLevel());
builder.sapphireLevel(tooltip.getSapphireLevel());
builder.diamondLevel(tooltip.getDiamondLevel());
builder.emeraldLevel(tooltip.getEmeraldLevel());
builder.rubyLevel(tooltip.getRubyLevel());
builder.topazLevel(tooltip.getTopazLevel());
if (tooltip.getBonus() > 0) {
builder.gemBonus(Integer.toString(tooltip.getBonus()));
}
Element toolTip = builder.build(parentNifty, parentScreen, toolTipLayer);
toolTip.getParent().layoutElements();
if (toolTip.getHeight() == 0) {
parentNifty.update();
toolTip.getParent().layoutElements();
}
int toolTipWidth = toolTip.getWidth();
int toolTipHeight = toolTip.getHeight();
boolean topSide = (location.getBottom() - toolTipHeight) > 0;
boolean rightSide = (location.getRight() - toolTipWidth) < 0;
if (topSide) {
toolTip.setConstraintY(SizeValue.px(location.getBottom() - toolTip.getHeight()));
} else {
toolTip.setConstraintY(SizeValue.px(location.getTop()));
}
if (rightSide) {
toolTip.setConstraintX(SizeValue.px(location.getLeft()));
} else {
toolTip.setConstraintX(SizeValue.px(location.getRight() - toolTip.getWidth()));
}
toolTip.getParent().layoutElements();
}
}