From 10d989fbc2881e0b763808054e1916fd20454f18 Mon Sep 17 00:00:00 2001 From: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Date: Tue, 20 Sep 2022 18:25:56 +0300 Subject: [PATCH] Support for custom menu buttons (#7595) * added support for custom main menu buttons * layout fix * ...and yet another fix * removed another unneccesary table, which was causing issues * actually, it looks better with even and odd buttons swapped * made the container a scroll pane, needs some testing... * this seems to work correctly * render the logo under the menu buttons * rename Buttoni to MenuButton --- .../mindustry/ui/fragments/MenuFragment.java | 204 +++++++++++------- 1 file changed, 123 insertions(+), 81 deletions(-) diff --git a/core/src/mindustry/ui/fragments/MenuFragment.java b/core/src/mindustry/ui/fragments/MenuFragment.java index bd8f34d3b1..99a2034b97 100644 --- a/core/src/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/mindustry/ui/fragments/MenuFragment.java @@ -12,7 +12,9 @@ import arc.scene.ui.*; import arc.scene.ui.ImageButton.*; import arc.scene.ui.TextButton.*; import arc.scene.ui.layout.*; +import arc.struct.*; import arc.util.*; +import mindustry.annotations.Annotations.*; import mindustry.core.*; import mindustry.game.EventType.*; import mindustry.gen.*; @@ -26,6 +28,7 @@ public class MenuFragment{ private Table container, submenu; private Button currentMenu; private MenuRenderer renderer; + private Seq customButtons = new Seq<>(); public void build(Group parent){ renderer = new MenuRenderer(); @@ -39,46 +42,6 @@ public class MenuFragment{ parent.fill((x, y, w, h) -> renderer.render()); - parent.fill(c -> { - container = c; - c.name = "menu container"; - - if(!mobile){ - buildDesktop(); - Events.on(ResizeEvent.class, event -> buildDesktop()); - }else{ - buildMobile(); - Events.on(ResizeEvent.class, event -> buildMobile()); - } - }); - - parent.fill(c -> c.bottom().right().button(Icon.discord, new ImageButtonStyle(){{ - up = discordBanner; - }}, ui.discord::show).marginTop(9f).marginLeft(10f).tooltip("@discord").size(84, 45).name("discord")); - - //info icon - if(mobile){ - parent.fill(c -> c.bottom().left().button("", new TextButtonStyle(){{ - font = Fonts.def; - fontColor = Color.white; - up = infoBanner; - }}, ui.about::show).size(84, 45).name("info")); - - - }else if(becontrol.active()){ - parent.fill(c -> c.bottom().right().button("@be.check", Icon.refresh, () -> { - ui.loadfrag.show(); - becontrol.checkUpdate(result -> { - ui.loadfrag.hide(); - if(!result){ - ui.showInfo("@be.noupdates"); - } - }); - }).size(200, 60).name("becheck").update(t -> { - t.getLabel().setColor(becontrol.isUpdateAvailable() ? Tmp.c1.set(Color.white).lerp(Pal.accent, Mathf.absin(5f, 1f)) : Color.white); - })); - } - String versionText = ((Version.build == -1) ? "[#fc8140aa]" : "[#ffffffba]") + Version.combined(); parent.fill((x, y, w, h) -> { TextureRegion logo = Core.atlas.find("logo"); @@ -96,6 +59,49 @@ public class MenuFragment{ Fonts.outline.setColor(Color.white); Fonts.outline.draw(versionText, fx, fy - logoh/2f - Scl.scl(2f), Align.center); }).touchable = Touchable.disabled; + + parent.fill(c -> { + c.pane(Styles.noBarPane, cont -> { + container = cont; + cont.name = "menu container"; + + if(!mobile){ + c.left(); + buildDesktop(); + Events.on(ResizeEvent.class, event -> buildDesktop()); + }else{ + buildMobile(); + Events.on(ResizeEvent.class, event -> buildMobile()); + } + }).with(pane -> { + pane.setOverscroll(false, false); + }).grow(); + }); + + parent.fill(c -> c.bottom().right().button(Icon.discord, new ImageButtonStyle(){{ + up = discordBanner; + }}, ui.discord::show).marginTop(9f).marginLeft(10f).tooltip("@discord").size(84, 45).name("discord")); + + //info icon + if(mobile){ + parent.fill(c -> c.bottom().left().button("", new TextButtonStyle(){{ + font = Fonts.def; + fontColor = Color.white; + up = infoBanner; + }}, ui.about::show).size(84, 45).name("info")); + }else if(becontrol.active()){ + parent.fill(c -> c.bottom().right().button("@be.check", Icon.refresh, () -> { + ui.loadfrag.show(); + becontrol.checkUpdate(result -> { + ui.loadfrag.hide(); + if(!result){ + ui.showInfo("@be.noupdates"); + } + }); + }).size(200, 60).name("becheck").update(t -> { + t.getLabel().setColor(becontrol.isUpdateAvailable() ? Tmp.c1.set(Color.white).lerp(Pal.accent, Mathf.absin(5f, 1f)) : Color.white); + })); + } } private void buildMobile(){ @@ -116,23 +122,28 @@ public class MenuFragment{ mods = new MobileButton(Icon.book, "@mods", ui.mods::show), exit = new MobileButton(Icon.exit, "@quit", () -> Core.app.exit()); + Seq customs = customButtons.map(b -> new MobileButton(b.icon, b.text, b.runnable == null ? () -> {} : b.runnable)); + if(!Core.graphics.isPortrait()){ container.marginTop(60f); container.add(play); container.add(join); container.add(custom); container.add(maps); + // add odd custom buttons + for(int i = 1; i < customs.size; i += 2){ + container.add(customs.get(i)); + } container.row(); - container.table(table -> { - table.defaults().set(container.defaults()); - - table.add(editor); - table.add(tools); - - table.add(mods); - if(!ios) table.add(exit); - }).colspan(4); + container.add(editor); + container.add(tools); + container.add(mods); + // add even custom buttons (before the exit button) + for(int i = 0; i < customs.size; i += 2){ + container.add(customs.get(i)); + } + if(!ios) container.add(exit); }else{ container.marginTop(0f); container.add(play); @@ -144,13 +155,13 @@ public class MenuFragment{ container.add(editor); container.add(tools); container.row(); - - container.table(table -> { - table.defaults().set(container.defaults()); - - table.add(mods); - if(!ios) table.add(exit); - }).colspan(2); + container.add(mods); + // add custom buttons + for(int i = 0; i < customs.size; i++){ + container.add(customs.get(i)); + if(i % 2 == 0) container.row(); + } + if(!ios) container.add(exit); } } @@ -168,23 +179,23 @@ public class MenuFragment{ t.name = "buttons"; buttons(t, - new Buttoni("@play", Icon.play, - new Buttoni("@campaign", Icon.play, () -> checkPlay(ui.planet::show)), - new Buttoni("@joingame", Icon.add, () -> checkPlay(ui.join::show)), - new Buttoni("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)), - new Buttoni("@loadgame", Icon.download, () -> checkPlay(ui.load::show)) + new MenuButton("@play", Icon.play, + new MenuButton("@campaign", Icon.play, () -> checkPlay(ui.planet::show)), + new MenuButton("@joingame", Icon.add, () -> checkPlay(ui.join::show)), + new MenuButton("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)), + new MenuButton("@loadgame", Icon.download, () -> checkPlay(ui.load::show)) ), - new Buttoni("@database.button", Icon.menu, - new Buttoni("@schematics", Icon.paste, ui.schematics::show), - new Buttoni("@database", Icon.book, ui.database::show), - new Buttoni("@about.button", Icon.info, ui.about::show) + new MenuButton("@database.button", Icon.menu, + new MenuButton("@schematics", Icon.paste, ui.schematics::show), + new MenuButton("@database", Icon.book, ui.database::show), + new MenuButton("@about.button", Icon.info, ui.about::show) ), - new Buttoni("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("@workshop", Icon.steam, platform::openWorkshop) : null, - new Buttoni("@mods", Icon.book, ui.mods::show), - new Buttoni("@settings", Icon.settings, ui.settings::show), - new Buttoni("@quit", Icon.exit, Core.app::exit) + new MenuButton("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new MenuButton("@workshop", Icon.steam, platform::openWorkshop) : null, + new MenuButton("@mods", Icon.book, ui.mods::show), + new MenuButton("@settings", Icon.settings, ui.settings::show) ); - + buttons(t, customButtons.toArray(MenuButton.class)); + buttons(t, new MenuButton("@quit", Icon.exit, Core.app::exit)); }).width(width).growY(); container.table(background, t -> { @@ -199,7 +210,6 @@ public class MenuFragment{ } private void checkPlay(Runnable run){ - if(!mods.hasContentErrors()){ run.run(); }else{ @@ -222,8 +232,8 @@ public class MenuFragment{ submenu.actions(Actions.alpha(1f), Actions.alpha(0f, 0.2f, Interp.fade), Actions.run(() -> submenu.clearChildren())); } - private void buttons(Table t, Buttoni... buttons){ - for(Buttoni b : buttons){ + private void buttons(Table t, MenuButton... buttons){ + for(MenuButton b : buttons){ if(b == null) continue; Button[] out = {null}; out[0] = t.button(b.text, b.icon, Styles.flatToggleMenut, () -> { @@ -251,24 +261,56 @@ public class MenuFragment{ } } - private static class Buttoni{ - final Drawable icon; - final String text; - final Runnable runnable; - final Buttoni[] submenu; + /** Adds a custom button to the menu. */ + public void addButton(String text, Drawable icon, Runnable callback){ + addButton(new MenuButton(text, icon, callback)); + } - public Buttoni(String text, Drawable icon, Runnable runnable){ + /** Adds a custom button to the menu. */ + public void addButton(String text, Runnable callback){ + addButton(text, Styles.none, callback); + } + + /** + * Adds a custom button to the menu. + * If {@link MenuButton#submenu} is null or the player is on mobile, {@link MenuButton#runnable} is invoked on click. + * Otherwise, {@link MenuButton#submenu} is shown. + */ + public void addButton(MenuButton button){ + customButtons.add(button); + } + + /** Represents a menu button definition. */ + public static class MenuButton{ + public final Drawable icon; + public final String text; + /** Runnable ran when the button is clicked. Ignored on desktop if {@link #submenu} is not null. */ + public final Runnable runnable; + /** Submenu shown when this button is clicked. Used instead of {@link #runnable} on desktop. */ + public final @Nullable MenuButton[] submenu; + + /** Constructs a simple menu button, which behaves the same way on desktop and mobile. */ + public MenuButton(String text, Drawable icon, Runnable runnable){ this.icon = icon; this.text = text; this.runnable = runnable; this.submenu = null; } - public Buttoni(String text, Drawable icon, Buttoni... buttons){ + /** Constructs a button that runs the runnable when clicked on mobile or shows the submenu on desktop. */ + public MenuButton(String text, Drawable icon, Runnable runnable, MenuButton... submenu){ + this.icon = icon; + this.text = text; + this.runnable = runnable; + this.submenu = submenu; + } + + /** Comstructs a desktop-only button; used internally. */ + MenuButton(String text, Drawable icon, MenuButton... submenu){ this.icon = icon; this.text = text; this.runnable = () -> {}; - this.submenu = buttons; + this.submenu = submenu; } } }