public interface PlayerChatView {

    public Player getPlayer();

    public TopWindow getWindow();

    public boolean showWindow(TopWindow window);

    public boolean removeShownWindow();

    public void update();

    public boolean handleIncoming(Text message);

    public Optional<Text> transformOutgoing(CommandSource sender, Text originalOutgoing, ChatType type);

    public boolean handleCommand(String[] args);

    public void onRemove();

    public void initialize();

}
 public class PlayerContext {

    public final int height;
    public final int width;
    public final boolean forceUnicode;

    private final UUID playerUUID;
    private final TextUtils utils;

    public PlayerContext(Player player, int width, int height, boolean forceUnicode, FontData fontData) {
        this(player.getUniqueId(), width, height, forceUnicode, new TextUtils(fontData, forceUnicode, player.getUniqueId()));
        checkArgument(height >= 1, "Height must be at least one");
        checkArgument(width >= 1, "Width must be at least one");
    }

    private PlayerContext(UUID playerUuid, int width, int height, boolean forceUnicode, TextUtils utils) {
        this.playerUUID = playerUuid;
        this.width = width;
        this.height = height;
        this.forceUnicode = forceUnicode;
        this.utils = utils;
    }

    public Player getPlayer() {
        return Sponge.getServer().getPlayer(this.playerUUID).get();
    }

    public TextUtils utils() {
        return this.utils;
    }

    public PlayerContext withHeight(int height) {
        checkArgument(height >= 1, "Height must be at least one");
        if (height == this.height) {
            return this;
        }
        return new PlayerContext(this.playerUUID, this.width, height, this.forceUnicode, this.utils);
    }

    public PlayerContext withWidth(int width) {
        checkArgument(width >= 1, "Width must be at least one");
        if (width == this.width) {
            return this;
        }
        return new PlayerContext(this.playerUUID, width, this.height, this.forceUnicode, this.utils);
    }

    public PlayerContext withUnicode(boolean forceUnicode) {
        if (forceUnicode == this.forceUnicode) {
            return this;
        }
        return new PlayerContext(this.playerUUID, this.width, this.height, forceUnicode,
                new TextUtils(this.utils.getFontData(), forceUnicode, this.playerUUID));
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("player", this.getPlayer())
                .add("width", this.width)
                .add("height", this.height)
                .add("forceUnicode", this.forceUnicode)
                .toString();
    }

}
 public class AnchorPaneUI extends UIPane {

    public static final int ANCHOR_TOP = 1;
    public static final int ANCHOR_RIGHT = 2;
    public static final int ANCHOR_BOTTOM = 4;
    public static final int ANCHOR_LEFT = 8;

    private final Object2IntMap<UIComponent> constraints = new Object2IntOpenHashMap<>();

    public AnchorPaneUI() {
    }

    public AnchorPaneUI(UIComponent... children) {
        addChildren(children);
    }

    @Override
    public void draw(PlayerContext ctx, LineFactory lineFactory) {
        LineFactory top = new LineFactory();
        LineFactory unrestraint = new LineFactory();
        LineFactory bottom = new LineFactory();
        List<UIComponent> remainingUnrestraint = Lists.newArrayList();
        List<UIComponent> children = getChildren();
        for (UIComponent child : children) {
            int constraint = this.constraints.getInt(child);
            int prefHeight = child.getPrefHeight(ctx);
            if (hasFlag(constraint, ANCHOR_BOTTOM)) {
                child.draw(ctx.withHeight(prefHeight), bottom);
            }
            if (hasFlag(constraint, ANCHOR_TOP)) {
                child.draw(ctx.withHeight(prefHeight), top);
            }
            if (constraint == 0) {
                remainingUnrestraint.add(child);
            }
        }
        int remaining = lineFactory.linesRemaining(ctx) - top.currentLineCount() - bottom.currentLineCount();
        for (UIComponent child : remainingUnrestraint) {
            if (remaining <= 0) {
                break;
            }
            child.draw(ctx.withHeight(remaining), unrestraint);
            remaining = unrestraint.linesRemaining(ctx);
        }
        lineFactory.merge(top, ctx).merge(unrestraint, ctx).fillThenMerge(ctx, bottom);
    }

    private static boolean hasFlag(int value, int flag) {
        return (value & flag) == flag;
    }

    public void addWithConstraint(UIComponent component, int constraints) {
        if (hasFlag(constraints, ANCHOR_BOTTOM) && hasFlag(constraints, ANCHOR_TOP)) {
            throw new UnsupportedOperationException("Cannot anchor to TOP *and* BOTTOM yet");
        }
        getChildren().add(component);
        this.constraints.put(component, constraints);
    }

}
 public class LineFactory {

    private final List<Text> lines = Lists.newArrayList();

    public void appendNewLine(Text text, PlayerContext ctx) {
        this.lines.add(text);
    }

    public void insertNewLine(int index, Text text, PlayerContext ctx) {
        this.lines.add(index, text);
    }

    public void addAll(List<Text> lines, PlayerContext ctx) {
        for (Text line : lines) {
            appendNewLine(line, ctx);
        }
    }

    public void addAll(Text[] lines, PlayerContext ctx) {
        for (Text line : lines) {
            appendNewLine(line, ctx);
        }
    }

    public List<Text> getLines() {
        return this.lines;
    }

    public LineFactory merge(LineFactory other, PlayerContext ctx) {
        this.addAll(other.getLines(), ctx);
        return this;
    }

    protected int currentLineCount() {
        return this.lines.size();
    }

    public void fillBlank(PlayerContext ctx) {
        int remaining = linesRemaining(ctx);
        while (remaining-- > 0) {
            appendNewLine(Text.EMPTY, ctx);
        }
    }

    public int linesRemaining(PlayerContext ctx) {
        return ctx.height - this.currentLineCount();
    }

    public LineFactory fillThenMerge(PlayerContext ctx, LineFactory other) {
        List<Text> otherLines = other.getLines();
        int height = ctx.height - otherLines.size();
        if (height > 0) {
            fillBlank(ctx.withHeight(height));
        }
        addAll(otherLines, ctx);
        return this;
    }

}
 public interface UIComponent extends ITextDrawable {

    @Override
    default Text draw(PlayerContext ctx) {
        LineFactory factory = new LineFactory();
        draw(ctx, factory);
        factory.fillBlank(ctx);
        return Text.builder().append(Text.joinWith(Text.NEW_LINE, factory.getLines())).build();
    }

    void draw(PlayerContext ctx, LineFactory lineFactory);

    default int getPrefHeight(PlayerContext ctx) {
        return ctx.height;
    }

    default int getMinHeight(PlayerContext ctx) {
        return getPrefHeight(ctx);
    }

    default int getMaxHeight(PlayerContext ctx) {
        return getPrefHeight(ctx);
    }

    default int getPrefWidth(PlayerContext ctx) {
        return ctx.width;
    }

    default int getMinWidth(PlayerContext ctx) {
        return getPrefWidth(ctx);
    }

    default int getMaxWidth(PlayerContext ctx) {
        return getPrefWidth(ctx);
    }

}
 public class DefaultColumnRenderer implements TableColumnRenderer {

    @Override
    public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
        return ctx.utils().splitLines(Text.of(value), getPrefWidth());
    }

    @Override
    public int getPrefWidth() {
        return 75;
    }

}
 public class DefaultTableRenderer implements TableRenderer {

    public static final TableViewport DEFAULT_VIEWPORT = new TableViewport() {

        @Override
        public int getFirstRowIndex() {
            return 0;
        }

        @Override
        public int getFirstColumnIndex() {
            return 0;
        }
    };

    @Override
    public TableViewport getViewport() {
        return DEFAULT_VIEWPORT;
    }

    @Override
    public int getPrefBorderWidth(int columnIndex, int numColumns, PlayerContext ctx) {
        return ctx.utils().getWidth('─', false);
    }

    @Override
    public TableColumnRenderer createColumnRenderer(int columnIndex) {
        return new DefaultColumnRenderer();
    }

    @Override
    public int modifyMaxWidth(int index, int max, int numColumns, PlayerContext ctx) {
        int barWidth = ctx.utils().getWidth('│', false);
        int edgeWidth = ctx.utils().getWidth('┼', false);
        int dashWidth = ctx.utils().getWidth('─', false);
        int extraPad;
        // line up so content starts after edge piece
        if (!ctx.forceUnicode) {
            extraPad = edgeWidth - barWidth;
        } else {
            extraPad = (int) Math.ceil(edgeWidth / 2D) - barWidth;
        }
        // Make it a multiple of the dash width
        int rem = max % dashWidth;
        extraPad += dashWidth - rem;
        // Align bar to center of joiner
        if (ctx.forceUnicode) {
            extraPad += (int) Math.ceil(edgeWidth / 2D) - barWidth;
        }
        extraPad %= dashWidth;
        return max + extraPad;
    }

    protected int getCellAlignment(int row, int column) {
        return SwingConstants.LEFT;
    }

    @Override
    public Text applySideBorders(int rowIndex, List<Text> line, int[] colMaxWidths, PlayerContext ctx) {
        Text.Builder builder = Text.builder();
        Text bar = TextUtils.charCache('│');
        for (int i = 0; i < colMaxWidths.length; i++) {
            Text part = null;
            int partWidth = 0;
            if (i < line.size()) {
                part = line.get(i);
            }
            if (part != null) {
                partWidth = ctx.utils().getWidth(part);
            }
            StringBuilder spaces = new StringBuilder();
            TextUtils.padSpaces(spaces, colMaxWidths[i] - partWidth);
            builder.append(bar);
            int alignment = getCellAlignment(rowIndex, i);
            if (alignment == SwingConstants.RIGHT || alignment == SwingConstants.CENTER) {
                if (spaces.length() > 0) {
                    if (alignment == SwingConstants.CENTER) {
                        String half = spaces.substring(0, (int) (spaces.length() + 0.5) / 2);
                        spaces.delete(0, half.length());
                        builder.append(Text.of(half));
                    } else {
                        builder.append(Text.of(spaces.toString()));
                    }
                }
            }
            if (part != null) {
                builder.append(part);
            }
            if (alignment == SwingConstants.LEFT || alignment == SwingConstants.CENTER) {
                if (spaces.length() > 0) {
                    builder.append(Text.of(spaces.toString()));
                }
            }
            if (i == colMaxWidths.length - 1) {
                builder.append(bar);
            }
        }
        return builder.build();

    }

    @Override
    public int borderHeight() {
        return 1;
    }

    @Override
    public Text createBorder(TableModel model, int rowIndex, int[] colMaxWidths, PlayerContext ctx) {
        char left = '├';
        char right = '┤';
        char join = '┼';
        if (rowIndex == -1) {
            left = '┌';
            right = '┐';
            join = '┬';
        } else if (rowIndex == model.getRowCount() - 1) {
            left = '└';
            right = '┘';
            join = '┴';
        }
        Text.Builder lineBuilder = Text.builder();
        for (int i = 0; i < colMaxWidths.length; i++) {
            char edge = i == 0 ? left : join;
            int edgeWidth = ctx.utils().getWidth(edge, false);
            int width = colMaxWidths[i] + edgeWidth;
            if (i < colMaxWidths.length - 1) {
                ctx.utils().startAndRepeat(lineBuilder, edge, '─', width);
            } else {
                width += edgeWidth;
                ctx.utils().startRepeatTerminate(lineBuilder, edge, '─', right, width);
            }
        }
        return lineBuilder.build();
    }

}
 public interface TableColumnRenderer {

    List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx);

    int getPrefWidth();

    default int getMinWidth() {
        return getPrefWidth();
    }

}
 public interface TableModel {

    int getRowCount();

    int getColumnCount();

    Object getCellValue(int row, int column);

}
 public class TableScrollHelper {

    private final TableModel model;

    private int scrollOffset;

    public TableScrollHelper(TableModel model) {
        this.model = model;
    }

    public boolean scrollUp() {
        if (!canScrollUp()) {
            return false;
        }
        this.scrollOffset--;
        return true;
    }

    public boolean canScrollUp() {
        return this.scrollOffset > 0;
    }

    public boolean scrollDown() {
        if (!canScrollDown()) {
            return false;
        }
        this.scrollOffset++;
        return true;
    }

    public boolean canScrollDown() {
        return this.scrollOffset < this.model.getRowCount() - 1;
    }

    public int getScrollOffset() {
        return this.scrollOffset;
    }

    public boolean scrollToOffset(int offset) {
        if (offset < 0 || offset >= this.model.getRowCount()) {
            return false;
        }
        this.scrollOffset = offset;
        return true;
    }

    public void reset() {
        this.scrollOffset = 0;
    }

    public TableModel getModel() {
        return this.model;
    }

    public TableViewport createViewport() {
        return new TableViewport() {

            @Override
            public int getFirstRowIndex() {
                return TableScrollHelper.this.getScrollOffset();
            }

            @Override
            public int getFirstColumnIndex() {
                return 0;
            }
        };
    }

}
 public class TableUI implements UIComponent {

    private final TableModel model;
    private final TableRenderer renderer;
    private final List<TableColumnRenderer> columnRenderers = Lists.newArrayList();

    public TableUI(TableModel model, TableRenderer renderer) {
        this.model = model;
        this.renderer = renderer;
        initTable();
    }

    private void initTable() {
        for (int i = 0; i < this.model.getColumnCount(); i++) {
            this.columnRenderers.add(this.renderer.createColumnRenderer(i));
        }
    }

    @Override
    public int getPrefWidth(PlayerContext ctx) {
        int width = 0;
        List<TableColumnRenderer> columnRenderers2 = this.columnRenderers;
        for (int i = 0; i < columnRenderers2.size(); i++) {
            width += columnRenderers2.get(i).getPrefWidth();
            width += this.renderer.getPrefBorderWidth(i, columnRenderers2.size(), ctx);
        }
        return width;
    }

    @Override
    public int getMinWidth(PlayerContext ctx) {
        int width = 0;
        List<TableColumnRenderer> columnRenderers2 = this.columnRenderers;
        for (int i = 0; i < columnRenderers2.size(); i++) {
            width += columnRenderers2.get(i).getMinWidth();
            width += this.renderer.getMinBorderWidth(i, columnRenderers2.size(), ctx);
        }
        return width;
    }

    @Override
    public int getMinHeight(PlayerContext ctx) {
        return 1;
    }

    @Override
    public int getPrefHeight(PlayerContext ctx) {
        int numRows = this.model.getRowCount() - this.renderer.getViewport().getFirstRowIndex();
        return (numRows * 2) + 1;
    }

    @Override
    public void draw(PlayerContext ctx, LineFactory lineFactory) {
        TableViewport viewport = this.renderer.getViewport();
        Map<Integer, Integer> columnMaxWidths = Maps.newLinkedHashMap();
        Map<Integer, List<List<Text>>> rowLines = Maps.newLinkedHashMap();
        int lineCountInitial = 0;
        for (int row = viewport.getFirstRowIndex(); row < this.model.getRowCount(); row++) {
            List<List<Text>> cellLines = Lists.newArrayList();
            for (int column = viewport.getFirstColumnIndex(); column < this.model.getColumnCount(); column++) {
                Object value = this.model.getCellValue(row, column);
                int cOff = 0;
                while (this.columnRenderers.size() <= column) {
                    this.columnRenderers.add(this.renderer.createColumnRenderer(column + cOff++));
                }
                List<Text> lines = this.columnRenderers.get(column).renderCell(value, row, ctx.width, ctx);
                for (int i = 0; i < lines.size(); i++) {
                    Text line = lines.get(i);
                    int width = ctx.utils().getWidth(line);
                    columnMaxWidths.put(column, Math.max(columnMaxWidths.getOrDefault(column, 0), width));
                    List<Text> correctLine = i < cellLines.size() ? cellLines.get(i) : null;
                    if (correctLine == null) {
                        cellLines.add(i, correctLine = Lists.newArrayList());
                    }
                    while (correctLine.size() < column) {
                        correctLine.add(null);
                    }
                    correctLine.add(line);
                }
                rowLines.put(row, cellLines);
            }
            lineCountInitial += cellLines.size() + this.renderer.borderHeight();
            if (lineCountInitial > ctx.height) {
                List<List<Text>> overRowLines = rowLines.get(row);
                while (!overRowLines.isEmpty() && lineCountInitial > ctx.height) {
                    overRowLines.remove(overRowLines.size() - 1);
                    lineCountInitial--;
                }
                if (lineCountInitial > ctx.height) {
                    rowLines.remove(row);
                }
                break;
            }
        }
        Collection<Integer> colMaxes = columnMaxWidths.values();
        int[] colMaxWidths = new int[colMaxes.size()];
        int i = 0;
        for (Integer max : colMaxes) {
            colMaxWidths[i] = this.renderer.modifyMaxWidth(i++, max, colMaxes.size(), ctx);
        }
        Text border = this.renderer.createBorder(this.model, viewport.getFirstRowIndex() - 1, colMaxWidths, ctx);
        if (border != null) {
            lineFactory.appendNewLine(border, ctx);
        }
        for (Entry<Integer, List<List<Text>>> row : rowLines.entrySet()) {
            int rowIndex = row.getKey();
            List<List<Text>> rowLineList = row.getValue();
            for (List<Text> colGroup : rowLineList) {
                Text lineSegment = this.renderer.applySideBorders(rowIndex, colGroup, colMaxWidths, ctx);
                lineFactory.appendNewLine(lineSegment, ctx);
            }
            if (lineFactory.linesRemaining(ctx) > 0) {
                border = this.renderer.createBorder(this.model, rowIndex, colMaxWidths, ctx);
                if (border != null) {
                    lineFactory.appendNewLine(border, ctx);
                }
            }
        }
    }

}
 public class ExtraUtils {

    public static ClickAction<?> clickAction(Runnable action, Tab tab) {
        return clickAction(view -> {
            action.run();
            return true;
        }, tab);
    }

    public static ClickAction<?> clickAction(Consumer<PlayerChatView> consumer, Tab tab) {
        return clickAction(view -> {
            consumer.accept(view);
            return true;
        }, tab);
    }

    public static ClickAction<?> clickAction(BooleanSupplier action, Tab tab) {
        return clickAction((Predicate<PlayerChatView>) view -> action.getAsBoolean(), tab);
    }

    public static ClickAction<?> clickAction(Predicate<PlayerChatView> action, Tab tab) {
        return Utils.execClick(clickHandler(action, tab));
    }

    public static Consumer<PlayerChatView> clickHandler(Predicate<PlayerChatView> action, Tab tab) {
        return view -> {
            PlayerChatView unwrapped = ChatUI.unwrapView(view);
            if (!ChatUI.isTabActive(unwrapped, tab)) {
                return;
            }
            if (action.test(unwrapped)) {
                view.update();
            }
        };
    }

}

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.simon816.chatui.lib.PlayerChatView;
import com.simon816.chatui.lib.PlayerContext;
import com.simon816.chatui.ui.AnchorPaneUI;
import com.simon816.chatui.ui.LineFactory;
import com.simon816.chatui.ui.UIComponent;
import com.simon816.chatui.ui.table.DefaultColumnRenderer;
import com.simon816.chatui.ui.table.DefaultTableRenderer;
import com.simon816.chatui.ui.table.TableColumnRenderer;
import com.simon816.chatui.ui.table.TableModel;
import com.simon816.chatui.ui.table.TableScrollHelper;
import com.simon816.chatui.ui.table.TableUI;
import com.simon816.chatui.util.ExtraUtils;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.service.permission.SubjectReference;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.action.TextActions;
import org.spongepowered.api.text.format.TextColor;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.text.format.TextStyles;
import org.spongepowered.api.util.Tristate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import javax.swing.SwingConstants;

package com.simon816.chatui.tabs.perm;




class SubjectViewer extends AnchorPaneUI {

    final PermissionsTab tab;

    Subject activeSubj;
    Set<Context> activeContext = Collections.emptySet();

    private List<Entry<String, Boolean>> permList;

    TableScrollHelper tableScroll;

    boolean addMode;
    boolean addParentMode;

    public SubjectViewer(PermissionsTab tab) {
        this.tab = tab;
        addWithConstraint(createTopBar(), ANCHOR_TOP);
        addChildren(createTable(), createEmptyMessage());
        addWithConstraint(createBottomBar(), ANCHOR_BOTTOM);
    }

    public List<Entry<String, Boolean>> getPerms() {
        if (this.permList == null) {
            Map<String, Boolean> map = this.activeSubj.getSubjectData().getPermissions(this.activeContext);
            this.permList = Lists.newArrayList();
            for (Entry<String, Boolean> entry : map.entrySet()) {
                this.permList.add(entry);
            }
        }
        this.permList.sort((a, b) -> a.getKey().compareToIgnoreCase(b.getKey()));
        return this.permList;
    }

    private TableModel createTableModel() {
        return new TableModel() {

            @Override
            public int getRowCount() {
                return getPerms().size();
            }

            @Override
            public int getColumnCount() {
                return 3;
            }

            @Override
            public Object getCellValue(int row, int column) {
                Entry<String, Boolean> entry = getPerms().get(row);
                if (column == 0) {
                    return entry.getKey();
                } else if (column == 1) {
                    return entry.getValue();
                }
                return null;
            }
        };
    }

    private TableUI createTable() {
        TableModel model = createTableModel();
        this.tableScroll = new TableScrollHelper(model);
        return new TableUI(model, new DefaultTableRenderer() {

            private final TableViewport viewport = SubjectViewer.this.tableScroll.createViewport();

            @Override
            public TableViewport getViewport() {
                return this.viewport;
            }

            @Override
            protected int getCellAlignment(int row, int column) {
                if (column == 2) {
                    return SwingConstants.CENTER;
                }
                return super.getCellAlignment(row, column);
            }

            @Override
            public TableColumnRenderer createColumnRenderer(int columnIndex) {
                if (columnIndex == 0) {
                    return new DefaultColumnRenderer() {

                        @Override
                        public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
                            return ctx.utils().splitLines(Text.of(ExtraUtils.clickAction(view -> {
                                toggle(view.getPlayer(), row);
                            }, SubjectViewer.this.tab), value), tableWidth - getPrefWidth());
                        }
                    };
                }
                if (columnIndex == 1) {
                    return new DefaultColumnRenderer() {

                        @Override
                        public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
                            boolean v = (Boolean) value;
                            TextColor color = v ? TextColors.GREEN : TextColors.RED;
                            return ctx.utils().splitLines(Text.of(color, ExtraUtils.clickAction(view -> {
                                toggle(view.getPlayer(), row);
                            }, SubjectViewer.this.tab), value), getPrefWidth());
                        }
                    };
                }
                if (columnIndex == 2) {
                    return new DefaultColumnRenderer() {

                        @Override
                        public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
                            return Collections.singletonList(Text.of(TextColors.RED, ExtraUtils.clickAction(view -> {
                                delete(view.getPlayer(), row);
                            }, SubjectViewer.this.tab), "X"));
                        }
                    };
                }
                return super.createColumnRenderer(columnIndex);
            }
        });
    }

    void toggle(Player player, int index) {
        Entry<String, Boolean> entry = getPerms().get(index);
        if (set(player, index, Tristate.fromBoolean(!entry.getValue())).join()) {
            this.permList = null;
        }
    }

    private CompletableFuture<Boolean> set(Player player, int index, Tristate value) {
        Entry<String, Boolean> entry = getPerms().get(index);
        return this.tab.actions().setPermission(player, this.activeSubj, this.activeContext, entry.getKey(), value);
    }

    void delete(Player player, int index) {
        if (set(player, index, Tristate.UNDEFINED).join()) {
            getPerms().remove(index);
        }
    }

    private UIComponent createEmptyMessage() {
        return new UIComponent() {

            @Override

            public void draw(PlayerContext ctx, LineFactory lineFactory) {
