package com.github.czyzby.lml.vis.parser.impl.tag;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays;
import com.github.czyzby.lml.parser.LmlParser;
import com.github.czyzby.lml.parser.action.ActorConsumer;
import com.github.czyzby.lml.parser.impl.attribute.table.OneColumnLmlAttribute;
import com.github.czyzby.lml.parser.impl.tag.AbstractActorLmlTag;
import com.github.czyzby.lml.parser.tag.LmlActorBuilder;
import com.github.czyzby.lml.parser.tag.LmlTag;
import com.github.czyzby.lml.util.LmlUtilities;
import com.github.czyzby.lml.vis.ui.VisTabTable;
import com.github.czyzby.lml.vis.ui.reflected.action.TabShowingAction;
import com.kotcrab.vis.ui.widget.VisTable;
import com.kotcrab.vis.ui.widget.tabbedpane.Tab;
import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPane;
import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPane.TabbedPaneTable;
import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPaneAdapter;
/** Handles {@link TabbedPane}. Allows to use table attributes: settings will be applied to tabbed pane's main table.
* Its children, though, should not have any cell attributes; in fact, this widget is prepared only to handle tab
* children - see {@link TabLmlTag}. Cannot parse plain text between tags. Note that tabbed pane tag cannot handle
* {@link OneColumnLmlAttribute} properly. {@link TabbedPane} is not actually an actor - if you want to inject the pane
* by its ID, use {@link TabbedPaneTable} instead and extract {@link TabbedPane} instance with
* {@link TabbedPaneTable#getTabbedPane()}. Mapped to "tabbedPane".
*
* @author MJ
* @see #getContentTable(TabbedPane) */
public class TabbedPaneLmlTag extends AbstractActorLmlTag {
private TabbedPane tabbedPane;
private boolean attachDefaultListener = true;
private ActorConsumer<Action, Tab> showActionProvider;
private ActorConsumer<Action, Tab> hideActionProvider;
public TabbedPaneLmlTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) {
super(parser, parentTag, rawTagData);
}
@Override
protected Actor getNewInstanceOfActor(final LmlActorBuilder builder) {
tabbedPane = new TabbedPane(builder.getStyleName());
final TabbedPaneTable mainTable = tabbedPane.getTable();
// TabbedPane will be accessible through LmlUserObject#getData(). This disables oneColumn attribute, though.
LmlUtilities.getLmlUserObject(mainTable).setData(tabbedPane);
if (tabbedPane.getTabsPane().isHorizontal() || tabbedPane.getTabsPane().isHorizontalFlow()) {
mainTable.row();
}
// This will be the content table:
mainTable.add(new VisTable()).grow().row();
// There might be an expand+fill image in the second cell. We need to correct that:
normalizeSecondCell(mainTable);
return mainTable;
}
/** @return {@link TabbedPane} main table, casted for convenience. */
protected TabbedPaneTable getTable() {
return (TabbedPaneTable) getActor();
}
/** @return managed {@link TabbedPane} extracted from its main table. */
protected TabbedPane getTabbedPane() {
return getTable().getTabbedPane();
}
/** @return managed {@link TabbedPane} instance. */
@Override
public Object getManagedObject() {
return tabbedPane;
}
/** @param mainTable main table of {@link TabbedPane}. */
protected void normalizeSecondCell(final Table mainTable) {
if (GdxArrays.sizeOf(mainTable.getCells()) < 2) {
return;
}
final Cell<?> secondCell = mainTable.getCells().get(1);
if (secondCell.getActor() instanceof Image) {
secondCell.expand(true, false);
secondCell.fill(true, false);
}
}
@Override
protected void handlePlainTextLine(final String plainTextLine) {
getParser().throwErrorIfStrict(
"TabbedPane cannot handle plain text. It can contain only tab children. Found plain text line: "
+ plainTextLine);
}
@Override
protected void handleValidChild(final LmlTag childTag) {
if (childTag.getActor() instanceof VisTabTable) {
final VisTabTable child = (VisTabTable) childTag.getActor();
getTabbedPane().add(child.getTab());
if (child.isDisabled()) {
getTabbedPane().disableTab(child.getTab(), true);
}
} else {
getParser().throwErrorIfStrict(
"TabbedPane cannot handle all actors. It can contain only tab children. Found child: "
+ childTag.getActor() + " with tag name: " + childTag.getTagName());
}
}
@Override
protected void doOnTagClose() {
if (attachDefaultListener) {
LmlUtilities.getLmlUserObject(getTable()).addOnCloseAction(getListenerAttachmentAction());
}
}
/** @return an on-close action that attached the default listener and fills content table with the active tab's
* content. */
protected ActorConsumer<Object, Object> getListenerAttachmentAction() {
return new ActorConsumer<Object, Object>() {
@Override
public Object consume(final Object actor) {
final TabbedPane tabbedPane = getTabbedPane();
// Invoked in a separate action to insure that the widget is truly fully built:
getContentTable(tabbedPane).add(tabbedPane.getActiveTab().getContentTable()).grow();
tabbedPane.addListener(new LmlTabbedPaneListener(tabbedPane, showActionProvider, hideActionProvider,
tabbedPane.getActiveTab()));
return null;
}
};
}
/** @param attachDefaultListener if true (default), will attach a default
* {@link com.kotcrab.vis.ui.widget.tabbedpane.TabbedPaneListener} that adds pane children to an internal
* table. This might be an undesired behavior if you want to take control over how tabs are processed, so
* set this setting to false if you need a custom listener. */
public void setAttachDefaultListener(final boolean attachDefaultListener) {
this.attachDefaultListener = attachDefaultListener;
}
/** @param showActionProvider will be invoked to provide an action for a tab each time a tab is shown. */
public void setShowActionProvider(final ActorConsumer<Action, Tab> showActionProvider) {
this.showActionProvider = showActionProvider;
}
/** @param hideActionProvider will be invoked to provide an action for a tab each time a tab is hidden. */
public void setHideActionProvider(final ActorConsumer<Action, Tab> hideActionProvider) {
this.hideActionProvider = hideActionProvider;
}
/** @param tabbedPane will have its content table extracted. Must have been created with a LML tag. This is were
* tabs are appended.
* @return content table of the tabbed pane. Might have to be cleared. */
public static Table getContentTable(final TabbedPane tabbedPane) {
final Array<Actor> children = tabbedPane.getTable().getChildren();
final Actor actor = children.get(children.size - 1);
if (!(actor instanceof Table)) {
throw new GdxRuntimeException(
"Tabbed pane not constructed with LML. Unable to find content table in: " + tabbedPane);
}
return (Table) actor;
}
/** Default listener of {@link TabbedPane} constructed with LML. Appends tab children to an internal
* {@link TabbedPane}'s table.
*
* @author MJ */
public static final class LmlTabbedPaneListener extends TabbedPaneAdapter {
private final TabbedPane tabbedPane;
private final ActorConsumer<Action, Tab> showActionProvider;
private final ActorConsumer<Action, Tab> hideActionProvider;
/** Cached tab for action utility. */
private Tab currentTab;
/** @param tabbedPane has to be created with a LML tag (or contain a separate content table as its last
* widget. */
public LmlTabbedPaneListener(final TabbedPane tabbedPane) {
this(tabbedPane, null, null, null);
}
/** @param tabbedPane has to be created with a LML tag (or contain a separate content table as its last widget.
* @param showActionProvider optional provider of showing actions. Will be used to show tabs.
* @param hideActionProvider optional provider of hiding actions. Will be used to hide tabs.
* @param initialTab current selected tab. Can be null. */
public LmlTabbedPaneListener(final TabbedPane tabbedPane, final ActorConsumer<Action, Tab> showActionProvider,
final ActorConsumer<Action, Tab> hideActionProvider, final Tab initialTab) {
this.tabbedPane = tabbedPane;
this.showActionProvider = showActionProvider;
this.hideActionProvider = hideActionProvider;
currentTab = initialTab;
}
@Override
public void switchedTab(final Tab tab) {
if (currentTab == null) {
// Immediate transition:
setNewTab(tab);
} else if (hideActionProvider == null) {
// Immediate transition:
setNewTab(tab);
} else {
// Showing after the hiding action is done:
final Table contentTable = getContentTable(tabbedPane);
contentTable.clearActions();
contentTable.addAction(Actions.sequence(hideActionProvider.consume(currentTab),
Actions.action(TabShowingAction.class).show(tab, this)));
}
}
/** @param tab will become currently set action. */
public void setNewTab(final Tab tab) {
final Table contentTable = getContentTable(tabbedPane);
contentTable.clear();
currentTab = tab;
if (tab != null) {
contentTable.add(tab.getContentTable()).grow();
if (showActionProvider != null) {
contentTable.addAction(showActionProvider.consume(tab));
}
} else {
contentTable.clear();
}
}
@Override
public void removedAllTabs() {
switchedTab(null);
}
}
}