I created a side bar of buttons for my game, the idea being that if you click on the main button of the group, the rest of the buttons in the group would pop out from the side of the screen.Later, I wanted to make it so that if you open one of the side menus the other ones would close.  I thought about implementing some booleans and methods that would iterate through the tables that comprised the side bar, closing them if they were open and were not the table associated with the button that the player clicked.  Instead I made something a little more object oriented.One complication here was that I needed some special logic to be called when only one of these buttons was clicked, specifically the Overlay button. When closed, I wanted any open color overlays to be automatically switched off, so that the player doesn't need to open up the menu again to switch it off.  Here are the buttons in action:SideButtonSlider.javapublic class SideButtonSlider {    private Table table;    //private float stageWidth;    private float stageHeight;    private GameScreen screen;    private boolean isOpen;    public SideButtonSlider(ArrayList<TextButton>buttons, GameScreen screen, Skin skin, String name, Color color, float stageWidth, float stageHeight) {        this.table = new Table(skin);        //this.stageWidth = stageWidth;        this.stageHeight = stageHeight;        this.screen = screen;        Table buttonTable = new Table();        buttonTable.defaults().pad(5).width(stageHeight/9).height(stageHeight/18).fill();        buttonTable.setFillParent(true);        for (Button button : buttons) {            buttonTable.add(button);            buttonTable.row();        }        this.table.add(buttonTable);        TextButton toggleButton = new TextButton(name, skin);        toggleButton.setColor(color);        toggleButton.addListener(new ClickListener() {            @Override            public void clicked(InputEvent event, float x, float y) {                SideButtonSlider.this.toggle();            }        });        Table toggleButtonTable = new Table(skin);        toggleButtonTable.defaults().pad(5).width(stageHeight/9).height(stageHeight/18).fill();        toggleButtonTable.setFillParent(true);        toggleButtonTable.add(toggleButton);        this.table.add(toggleButtonTable);        this.close();    }    public boolean isOpen() {        return this.isOpen;    }    public void toggle() {        if (this.isOpen) {            this.close();            this.screen.sliderClosed(this);        } else {            this.open();            this.screen.closeOpenSlidersExcept(this);        }    }    public void show() {        this.table.setVisible(true);    }    public void hide() {        this.table.setVisible(false);    }    public Table getTable() {        return this.table;    }    private void open() {        this.isOpen = true;        this.table.addAction(Actions.moveBy(this.stageHeight/7.8f, 0, 0.4f));    }    private void close() {        this.isOpen = false;        this.table.addAction(Actions.moveBy(-this.stageHeight/7.8f, 0, 0.4f));    }}Here is the usage in the GameScene class:public void createSideButtons() {    TextButton desireOverlayButton = new TextButton(Desire, this.skin);    desireOverlayButton.setColor(Color.BLUE);    desireOverlayButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.toggleDesireOverlay();        }    });    TextButton populationOverlayButton = new TextButton(Pop, this.skin);    populationOverlayButton.setColor(Color.BLUE);    populationOverlayButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.togglePopulationOverlay();        }    });    TextButton waterOverlayButton = new TextButton(Water, this.skin);    waterOverlayButton.setColor(Color.BLUE);    waterOverlayButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.toggleWaterOverlay();        }    });    ArrayList<TextButton>overlayButtons = new ArrayList<TextButton>();    overlayButtons.add(waterOverlayButton);    overlayButtons.add(populationOverlayButton);    overlayButtons.add(desireOverlayButton);    TextButton servicesButton = new TextButton(Service, this.skin);    servicesButton.setColor(Color.GREEN);    servicesButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.buildServicesMenu();        }    });    TextButton budgetButton = new TextButton(Budget, this.skin);    budgetButton.setColor(Color.GREEN);    budgetButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.buildBudgetMenu();        }    });    TextButton mayorButton = new TextButton(Mayor, skin);    mayorButton.setColor(Color.GREEN);    mayorButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.clickedMayorButton();        }    });        ArrayList<TextButton>moneyButtons = new ArrayList<TextButton>();    moneyButtons.add(budgetButton);    moneyButtons.add(servicesButton);    moneyButtons.add(mayorButton);    TextButton clippingButton = new TextButton(Clip, this.skin);    clippingButton.setColor(Color.YELLOW);    clippingButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.toggleClipping();        }    });    TextButton zoomButton = new TextButton(Zoom, this.skin);    zoomButton.setColor(Color.YELLOW);    zoomButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.toggleZoom();        }    });    ArrayList<TextButton>cameraButtons = new ArrayList<TextButton>();    cameraButtons.add(clippingButton);    cameraButtons.add(zoomButton);    TextButton pauseButton = new TextButton(Pause, this.skin);    pauseButton.setColor(Color.RED);    pauseButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.togglePause();        }    });    TextButton randomizeButton = new TextButton(Random, this.skin);    randomizeButton.setColor(Color.RED);    randomizeButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.game.world.randomlyUpdateWorld();        }    });    TextButton menuButton = new TextButton(Menu, this.skin);    menuButton.setColor(Color.RED);    menuButton.addListener(new ClickListener() {        @Override        public void clicked(InputEvent event, float x, float y) {            GameScreen.this.buildInGameMenu();        }    });    ArrayList<TextButton>gameButtons = new ArrayList<TextButton>();    gameButtons.add(pauseButton);    gameButtons.add(menuButton);    gameButtons.add(randomizeButton);    this.buttonTable = new Table(this.skin);    this.buttonTable.setPosition(-LibGDXGame.STAGE_HEIGHT/14, 0);    this.buttonTable.setFillParent(true);    this.buttonTable.left();    this.overlaySlider = new SideButtonSlider(overlayButtons, this, this.skin, Overlay, Color.BLUE, LibGDXGame.STAGE_WIDTH, LibGDXGame.STAGE_HEIGHT);    this.buttonTable.add(this.overlaySlider.getTable());    this.buttonTable.row();    this.moneySlider = new SideButtonSlider(moneyButtons, this, this.skin, Money, Color.GREEN, LibGDXGame.STAGE_WIDTH, LibGDXGame.STAGE_HEIGHT);    this.buttonTable.add(this.moneySlider.getTable());    this.buttonTable.row();    this.cameraSlider = new SideButtonSlider(cameraButtons, this, this.skin, Camera, Color.YELLOW, LibGDXGame.STAGE_WIDTH, LibGDXGame.STAGE_HEIGHT);    this.buttonTable.add(this.cameraSlider.getTable());    this.buttonTable.row();    this.gameSlider = new SideButtonSlider(gameButtons, this, this.skin, Game, Color.RED, LibGDXGame.STAGE_WIDTH, LibGDXGame.STAGE_HEIGHT);    this.buttonTable.add(this.gameSlider.getTable());    this.libGDXGame.hudStage.addActor(this.buttonTable);}And finally, the methods that control the special logic:public void closeOpenSlidersExcept(SideButtonSlider slider) {    if (slider != this.overlaySlider) {        if (this.overlaySlider.isOpen()) {            this.overlaySlider.toggle();        }    }    if (slider != this.moneySlider) {        if (this.moneySlider.isOpen()) {            this.moneySlider.toggle();        }    }    if (slider != this.cameraSlider) {        if (this.cameraSlider.isOpen()) {            this.cameraSlider.toggle();        }    }    if (slider != this.gameSlider) {        if (this.gameSlider.isOpen()) {            this.gameSlider.toggle();        }    }}public void sliderClosed(SideButtonSlider slider) {    if (slider == this.overlaySlider) {        this.libGDXGame.disableOverlays();        this.uncolorAllTileActors();    }}I know that I could separate all of this logic out into another class, a SideBar class perhaps, but all of these buttons call methods in the GameScreen class so that will require a lot of work. Not sure if it is worth it.I don't like that all of the buttons need to be created before this whole structure is put together, but again, all of the callbacks are pointing to private methods in the GameScreen class so this seemed like a reasonable way to do things.All feedback is appreciated, as always.
Take These Buttons Back
java;object oriented;game;gui;libgdx
You can literally use the builder pattern for your Builder game here. :)See, you have group of buttons that must be assigned a same color. These buttons have a corresponding pair of button label and a ClickListener implementation. Therefore, it's not hard to imagine a Builder implementation that lets you chain these operations:public final class SideButtonSliderBuilder {    private final GameScreen screen;    private final Skin skin;    private final String groupLabel;    private final Color color;    private final List<String> labels = new ArrayList<String>();    private final List<ClickListener> listeners = new ArrayList<String>();    private SideButtonSliderBuilder(GameScreen screen, Skin skin,                     String groupLabel, Color color) {        this.screen = screen;        this.skin = skin;        this.groupLabel = groupLabel;        this.color = color;    }    public SideButtonSliderBuilder addButton(String label, ClickListener listener) {        // remember null checks too, just in case        labels.add(label);        listeners.add(listener);        return this;    }    private TextButton toButton(String label, ClickListener listener) {        // since this is a private method, null checks are probably optional        TextButton button = new TextButton(label, skin);        button.setColor(color);        button.addListener(listener);        return button;    }    private List<TextButton> toList() {        List<TextButton> list = new ArrayList<TextButton>(labels.size());        for (Iterator<String> labelIterator = labels.iterator(),                    Iterator<ClickListener> listenerIterator = listeners.iterator();                labelIterator.hasNext() && listenerIterator().hasNext();) {            list.add(toButton(labelIterator.next(), listenrIterator.next()));        }        return list;    }    public SideButtonSlider build() {        // take note of List vs ArrayList, see below        return new SideButtonSlider(toList(), screen, skin, groupLabel, color,                         LibGDXGame.STAGE_WIDTH, LibGDXGame.STAGE_HEIGHT);    }    public static SideButtonSliderBuilder of(GameScreen screen, Skin skin,                    String groupLabel, Color color) {        return new SideButtonSliderBuilder(screen, skin, groupLabel, color);    }}In my opinion, the benefits of the builder pattern is that it lets you specify any common parameters once to minimize code repetition (and therefore bugs), and enforces the requirements for creating the necessary instances consistently too. In the long run, it also lets you introduce new features or fix bugs all in one go. Furthermore, builder classes tend to adopt the 'fluent' programming approach by using more descriptive sounding method names and return-ing itself, thereby allowing for daisy-chaining.An example usage will be:// inside your GameScene classSideButtonSliderBuilder cameraBuilder = SideButtonSliderBuilder.of(this, this.skin,                                                             Camera, Color.YELLOW);cameraBuilder.addButton(Clip, /* clip listener */)                .addButton(Zoom, /* zoom listener */);this.cameraSlider = cameraBuilder.build();You can even opt to daisy-chain the entire thing, from of() to calling build().Just two more things to highlight from my code above:// since this is a private method, null checks are probably optionalMy personal opinion is that private methods can be less... particular about null inputs, as you should be trusting enough of yourself/your team that you aren't introducing them in the first place. This is more true, I hope, when the method calling the private method is right next to it, such that there is even lesser concern that nulls will be introduced.// take note of List vs ArrayList, see belowDeclarations should be done with the interfaces, rather than the concrete implementation. This lets users of your variables simply use them as they are defined by the interface methods, rather than the exact type of implementing class. Therefore, I will suggest List<String> list = new ArrayList<String>().