From 6b334ed79ece20309857997f0eb2e5d0647f089c Mon Sep 17 00:00:00 2001 From: 2b2m22 Date: Thu, 7 May 2026 20:06:24 +0300 Subject: [PATCH 1/3] planetBackground --- .../java/mindurka/ui/OCustomRulesDialog.java | 2 + src/main/java/mindurka/ui/OEditorDialog.java | 6 +- .../mindurka/ui/PlanetBackgroundDialog.java | 190 ++++++++++++++++++ .../mindurka/ui/PlanetBackgroundDrawer.java | 66 ++++++ src/main/resources/bundles/bundle.properties | 9 +- .../resources/bundles/bundle_ru.properties | 7 + 6 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 src/main/java/mindurka/ui/PlanetBackgroundDialog.java create mode 100644 src/main/java/mindurka/ui/PlanetBackgroundDrawer.java diff --git a/src/main/java/mindurka/ui/OCustomRulesDialog.java b/src/main/java/mindurka/ui/OCustomRulesDialog.java index 23f01b4..72352f2 100644 --- a/src/main/java/mindurka/ui/OCustomRulesDialog.java +++ b/src/main/java/mindurka/ui/OCustomRulesDialog.java @@ -35,6 +35,7 @@ public class OCustomRulesDialog extends CustomRulesDialog { private RevealedContentDialog revealedBlocks = new RevealedContentDialog<>("@revealedblocks", ContentType.block, b -> !b.name.startsWith("tmp-")); private BannedContentDialog bannedUnits = new BannedContentDialog<>("@bannedunits", ContentType.unit, u -> true); private EnvDialog envDialog = new EnvDialog(); + private PlanetBackgroundDialog backgroundDialog = new PlanetBackgroundDialog(); private Table main; private RulesWrite writeRoot; @@ -181,6 +182,7 @@ void build() { write.button("rules.env", () -> envDialog.show()); write.button("rules.weather", () -> Reflect.invoke(CustomRulesDialog.class, this, "weatherDialog", Util.noargs)); + write.button("rules.planetbackground", () -> backgroundDialog.show()); write = writeRoot.category("planet"); write.selection("rules.title.planet", addItem -> { diff --git a/src/main/java/mindurka/ui/OEditorDialog.java b/src/main/java/mindurka/ui/OEditorDialog.java index 62154b8..6eaadca 100644 --- a/src/main/java/mindurka/ui/OEditorDialog.java +++ b/src/main/java/mindurka/ui/OEditorDialog.java @@ -787,13 +787,15 @@ private void buildMenu() { "@editor.importimage", "@editor.importimage.description", Icon.fileImage, (Runnable)() -> Vars.platform.showFileChooser(true, "png", file -> Vars.ui.loadAnd(() -> { + Pixmap pixmap = null; try{ - Pixmap pixmap = new Pixmap(file); + pixmap = new Pixmap(file); editor.OBeginEdit(pixmap); - pixmap.dispose(); }catch(Exception e){ Vars.ui.showException("@editor.errorload", e); Log.err(e); + }finally{ + if(pixmap != null) pixmap.dispose(); } }))) ); diff --git a/src/main/java/mindurka/ui/PlanetBackgroundDialog.java b/src/main/java/mindurka/ui/PlanetBackgroundDialog.java new file mode 100644 index 0000000..dd7bbcd --- /dev/null +++ b/src/main/java/mindurka/ui/PlanetBackgroundDialog.java @@ -0,0 +1,190 @@ +package mindurka.ui; + +import arc.Core; +import arc.graphics.Color; +import arc.graphics.Texture; +import arc.graphics.g2d.Draw; +import arc.input.KeyCode; +import arc.math.Mathf; +import arc.math.geom.Vec3; +import arc.scene.event.InputEvent; +import arc.scene.event.InputListener; +import arc.scene.ui.layout.Table; +import mindustry.graphics.Pal; +import mindustry.graphics.g3d.PlanetParams; +import mindustry.gen.Icon; +import mindustry.type.Planet; +import mindustry.ui.Fonts; +import mindustry.ui.Styles; +import mindustry.ui.dialogs.BaseDialog; + +import static mindustry.Vars.*; + +public class PlanetBackgroundDialog extends BaseDialog { + private Table main; + private PlanetParams params; + + private float rotX = 90f; + private float rotY = 90f; + private float zoom = 1f; + + private static final float DRAG_SPEED = 0.3f; + private static final float SCROLL_SPEED = 0.1f; + + public PlanetBackgroundDialog() { + super("@rules.planetbackground", new DialogStyle() {{ + stageBackground = Styles.none; + titleFont = Fonts.def; + titleFontColor = Pal.accent; + }}); + + addCloseButton(); + buttons.button("@rules.background.removebackground", Icon.trash, () -> { + if (state.rules.planetBackground != null) ui.showConfirm("@rules.background.removalwarning", this::removeBackground); + }).size(210f, 64f); + buttons.button("@rules.background.selectplanet", Icon.planet, this::planetDialog).size(210f, 64f); + + titleTable.row(); + titleTable.add("@rules.background.description").color(Color.white).padBottom(20f); + + addListener(new InputListener() { + private float lastX, lastY; + private boolean dragging = false; + + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button) { + if (state.rules.planetBackground == null || button != KeyCode.mouseRight) return false; + lastX = x; + lastY = y; + dragging = true; + return true; + } + + @Override + public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button) { + dragging = false; + } + + @Override + public void touchDragged(InputEvent event, float x, float y, int pointer) { + if (!dragging || state.rules.planetBackground == null) return; + float dx = x - lastX; + float dy = y - lastY; + lastX = x; + lastY = y; + + rotX = Mathf.mod(rotX + dx * DRAG_SPEED, 360f); + rotY = Mathf.clamp(rotY + dy * DRAG_SPEED, 1f, 179f); + updateRotation(); + } + + @Override + public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY) { + if (state.rules.planetBackground == null) return false; + zoom = Mathf.clamp(zoom * (1f + amountY * SCROLL_SPEED), 0.1f, 10f); + updateRotation(); + return true; + } + }); + + shown(this::setup); + } + + private void addBackground() { + rotX = 90f; + rotY = 90f; + zoom = 1f; + state.rules.planetBackground = new PlanetParams(); + params = state.rules.planetBackground; + updateRotation(); + setup(); + } + + private void removeBackground() { + state.rules.planetBackground = null; + PlanetBackgroundDrawer.update(); + setup(); + } + + private void addPlanetButton(Planet planet, Table tb, BaseDialog dialog) { + tb.button(planet.localizedName, Icon.planet, Styles.togglet, () -> { + params.planet = planet; + updateRotation(); + dialog.hide(); + }).marginLeft(14f).padBottom(5f).width(220f).height(55f) + .checked(params.planet == planet) + .update(b -> b.setChecked(params.planet == planet)) + .get().getChildren().get(1).setColor(planet.iconColor); + } + + private void planetDialog() { + if (params == null) return; + + BaseDialog dialog = new BaseDialog("@rules.background.selectplanet"); + dialog.cont.pane(p -> p.table(t -> { + int i = 0; + for (Planet planet : content.planets()) { + addPlanetButton(planet, t, dialog); + if (++i % 3 == 0) t.row(); + } + })); + dialog.addCloseButton(); + dialog.show(); + } + + private void setup() { + params = state.rules.planetBackground; + + if (params != null) { + zoom = params.zoom; + if (params.camPos != null && !params.camPos.isZero()) { + Vec3 dir = params.camPos.cpy().nor(); + rotY = (float)Math.acos(dir.y) * Mathf.radDeg; + rotX = Mathf.atan2(dir.z, dir.x) * Mathf.radDeg; + } + } + + cont.clear(); + cont.table(t -> main = t); + + if (params == null) { + main.add("@rules.background.nobackground").color(Color.white).padBottom(20f).row(); + main.button("@rules.background.addbackground", Styles.togglet, this::addBackground) + .marginLeft(14f).width(220f).height(55f); + } + + updateRotation(); + } + + private void updateRotation() { + PlanetParams p = state.rules.planetBackground; + if (p == null) return; + p.camPos = new Vec3( + Mathf.cosDeg(rotX) * Mathf.sinDeg(rotY), + Mathf.cosDeg(rotY), + Mathf.sinDeg(rotX) * Mathf.sinDeg(rotY) + ); + p.zoom = zoom; + PlanetBackgroundDrawer.update(); + } + + @Override + public void draw() { + Texture tex = params != null ? PlanetBackgroundDrawer.draw() : null; + if (tex != null) { + float drawSize = Math.max(Core.graphics.getWidth(), Core.graphics.getHeight()); + Draw.rect( + Draw.wrap(tex), + Core.graphics.getWidth() / 2f, + Core.graphics.getHeight() / 2f, + drawSize, -drawSize + ); + Draw.flush(); + } else { + Draw.color(color.r, color.g, color.b, color.a * parentAlpha); + Styles.black9.draw(x, y, width, height); + } + + super.draw(); + } +} diff --git a/src/main/java/mindurka/ui/PlanetBackgroundDrawer.java b/src/main/java/mindurka/ui/PlanetBackgroundDrawer.java new file mode 100644 index 0000000..e1fc3cb --- /dev/null +++ b/src/main/java/mindurka/ui/PlanetBackgroundDrawer.java @@ -0,0 +1,66 @@ +package mindurka.ui; + +import arc.Core; +import arc.graphics.Color; +import arc.graphics.Texture; +import arc.graphics.gl.FrameBuffer; +import arc.util.Nullable; +import mindustry.graphics.g3d.PlanetParams; +import mindustry.graphics.g3d.PlanetRenderer; + +import static mindustry.Vars.*; + +public class PlanetBackgroundDrawer { + private static @Nullable FrameBuffer backgroundBuffer; + private static final PlanetRenderer planets = new PlanetRenderer(); + + private static boolean changed = true; + + public static void update() { + changed = true; + } + + public static @Nullable Texture draw() { + if (state.rules.planetBackground == null) { + dispose(); + return null; + } + + int size = Math.max(Core.graphics.getWidth(), Core.graphics.getHeight()); + + boolean resized = false; + if (backgroundBuffer == null) { + resized = true; + backgroundBuffer = new FrameBuffer(size, size); + } + + if (changed || resized || backgroundBuffer.resizeCheck(size, size)) { + changed = false; + + backgroundBuffer.begin(Color.clear); + + PlanetParams params = state.rules.planetBackground; + params.viewW = size; + params.viewH = size; + params.alwaysDrawAtmosphere = true; + params.drawUi = false; + + if (params.camPos == null || params.camPos.isZero()) { + params.camPos = new arc.math.geom.Vec3(0f, 0f, 1f); + } + + planets.render(params); + + backgroundBuffer.end(); + } + + return backgroundBuffer.getTexture(); + } + + public static void dispose() { + if (backgroundBuffer != null) { + backgroundBuffer.dispose(); + backgroundBuffer = null; + } + } +} diff --git a/src/main/resources/bundles/bundle.properties b/src/main/resources/bundles/bundle.properties index 2468a80..88138e1 100644 --- a/src/main/resources/bundles/bundle.properties +++ b/src/main/resources/bundles/bundle.properties @@ -150,4 +150,11 @@ plotstate.locked = Plot is placed until the team is destroyed plotstate.static = Plot cannot be sunk plotstate.ghost = Game pretends there's a plot there -settings.mindurka = Mindurka \ No newline at end of file +settings.mindurka = Mindurka + +rules.planetbackground = Planet Background +rules.background.selectplanet = Select Planet +rules.background.nobackground = No background set. +rules.background.addbackground = Add Background +rules.background.removebackground = Remove Background +rules.background.removalwarning = Are you sure you want to remove the planet background? \ No newline at end of file diff --git a/src/main/resources/bundles/bundle_ru.properties b/src/main/resources/bundles/bundle_ru.properties index 446bfbe..b0b21e0 100644 --- a/src/main/resources/bundles/bundle_ru.properties +++ b/src/main/resources/bundles/bundle_ru.properties @@ -135,3 +135,10 @@ plotstate.placed = Поставить плот plotstate.locked = Плот существует пока команда не уничтожена plotstate.static = Статический плот (Плот не может быть уничтожен/потоплен) plotstate.ghost = Призрачный плот (Статичексий плот без его схематики) + +rules.planetbackground = Фон планеты +rules.background.selectplanet = Выбрать планету +rules.background.nobackground = Фон не задан +rules.background.addbackground = Добавить фон +rules.background.removebackground = Удалить фон +rules.background.removalwarning = Вы уверены, что хотите удалить фон планеты? From c2f306cad0546990359217386b9805c02ba5dd84 Mon Sep 17 00:00:00 2001 From: 2b2m22 Date: Thu, 7 May 2026 21:48:16 +0300 Subject: [PATCH 2/3] fix --- src/main/java/mindurka/ui/PlanetBackgroundDialog.java | 3 +-- src/main/resources/bundles/bundle.properties | 3 ++- src/main/resources/bundles/bundle_ru.properties | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/mindurka/ui/PlanetBackgroundDialog.java b/src/main/java/mindurka/ui/PlanetBackgroundDialog.java index dd7bbcd..c05b4be 100644 --- a/src/main/java/mindurka/ui/PlanetBackgroundDialog.java +++ b/src/main/java/mindurka/ui/PlanetBackgroundDialog.java @@ -96,7 +96,6 @@ private void addBackground() { zoom = 1f; state.rules.planetBackground = new PlanetParams(); params = state.rules.planetBackground; - updateRotation(); setup(); } @@ -140,7 +139,7 @@ private void setup() { if (params.camPos != null && !params.camPos.isZero()) { Vec3 dir = params.camPos.cpy().nor(); rotY = (float)Math.acos(dir.y) * Mathf.radDeg; - rotX = Mathf.atan2(dir.z, dir.x) * Mathf.radDeg; + rotX = Mathf.mod(Mathf.atan2(dir.z, dir.x) * Mathf.radDeg, 360f); } } diff --git a/src/main/resources/bundles/bundle.properties b/src/main/resources/bundles/bundle.properties index 88138e1..676b38a 100644 --- a/src/main/resources/bundles/bundle.properties +++ b/src/main/resources/bundles/bundle.properties @@ -154,7 +154,8 @@ settings.mindurka = Mindurka rules.planetbackground = Planet Background rules.background.selectplanet = Select Planet -rules.background.nobackground = No background set. +rules.background.description = Right-click drag to rotate. Scroll to zoom. +rules.background.nobackground = No background set rules.background.addbackground = Add Background rules.background.removebackground = Remove Background rules.background.removalwarning = Are you sure you want to remove the planet background? \ No newline at end of file diff --git a/src/main/resources/bundles/bundle_ru.properties b/src/main/resources/bundles/bundle_ru.properties index b0b21e0..b187f16 100644 --- a/src/main/resources/bundles/bundle_ru.properties +++ b/src/main/resources/bundles/bundle_ru.properties @@ -138,6 +138,7 @@ plotstate.ghost = Призрачный плот (Статичексий плот rules.planetbackground = Фон планеты rules.background.selectplanet = Выбрать планету +rules.background.description = ПКМ + перетащить для вращения. Колёсико мыши — масштаб. rules.background.nobackground = Фон не задан rules.background.addbackground = Добавить фон rules.background.removebackground = Удалить фон From 0fcf81e4c38396ae6eb595debeec0fcc20f3fe6e Mon Sep 17 00:00:00 2001 From: 2b2m22 Date: Sat, 9 May 2026 12:48:39 +0300 Subject: [PATCH 3/3] Texture --- .../mindurka/ui/PlanetBackgroundDialog.java | 402 +++++++++++++++++- src/main/resources/bundles/bundle.properties | 16 +- .../resources/bundles/bundle_ru.properties | 16 +- 3 files changed, 416 insertions(+), 18 deletions(-) diff --git a/src/main/java/mindurka/ui/PlanetBackgroundDialog.java b/src/main/java/mindurka/ui/PlanetBackgroundDialog.java index c05b4be..4a67dd0 100644 --- a/src/main/java/mindurka/ui/PlanetBackgroundDialog.java +++ b/src/main/java/mindurka/ui/PlanetBackgroundDialog.java @@ -1,15 +1,31 @@ package mindurka.ui; import arc.Core; +import arc.files.Fi; import arc.graphics.Color; +import arc.graphics.Pixmap; import arc.graphics.Texture; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.nio.ByteBuffer; import arc.graphics.g2d.Draw; +import arc.graphics.g2d.TextureRegion; import arc.input.KeyCode; import arc.math.Mathf; import arc.math.geom.Vec3; import arc.scene.event.InputEvent; import arc.scene.event.InputListener; +import arc.scene.style.TextureRegionDrawable; +import arc.scene.ui.ButtonGroup; +import arc.scene.ui.ImageButton; +import arc.scene.ui.ScrollPane; +import arc.scene.ui.TextButton; +import arc.scene.ui.layout.Scl; import arc.scene.ui.layout.Table; +import arc.struct.Seq; +import mindustry.gen.Tex; import mindustry.graphics.Pal; import mindustry.graphics.g3d.PlanetParams; import mindustry.gen.Icon; @@ -28,6 +44,11 @@ public class PlanetBackgroundDialog extends BaseDialog { private float rotY = 90f; private float zoom = 1f; + private boolean textureMode = false; + private Table descriptionRow; + private String savedTexture; + private Texture previewTexture; + private static final float DRAG_SPEED = 0.3f; private static final float SCROLL_SPEED = 0.1f; @@ -38,14 +59,38 @@ public PlanetBackgroundDialog() { titleFontColor = Pal.accent; }}); + titleTable.row(); + descriptionRow = new Table(); + descriptionRow.add("@rules.background.description").color(Color.white).padBottom(20f); + titleTable.add(descriptionRow).colspan(10); + titleTable.row(); + titleTable.add().growX(); + titleTable.table(modeTable -> { + modeTable.defaults().width(140f).height(45f); + modeTable.button("@rules.background.mode.planet", Styles.flatTogglet, () -> { + if (!textureMode) return; + addPlanetBackground(); + }).update(b -> b.setChecked(!textureMode)).row(); + modeTable.button("@rules.background.mode.texture", Styles.flatTogglet, () -> { + if (textureMode) return; + addTextureBackground(); + }).update(b -> b.setChecked(textureMode)).row(); + }).padRight(10f); + addCloseButton(); + buttons.button("", Icon.planet, () -> { + if (textureMode) textureDialog(); + else planetDialog(); + }).size(210f, 64f).update(b -> { + b.setText(textureMode + ? Core.bundle.get("rules.background.selecttexture") + : Core.bundle.get("rules.background.selectplanet")); + }); buttons.button("@rules.background.removebackground", Icon.trash, () -> { - if (state.rules.planetBackground != null) ui.showConfirm("@rules.background.removalwarning", this::removeBackground); + if (hasBackground()) { + ui.showConfirm("@rules.background.removalwarning", this::removeBackground); + } }).size(210f, 64f); - buttons.button("@rules.background.selectplanet", Icon.planet, this::planetDialog).size(210f, 64f); - - titleTable.row(); - titleTable.add("@rules.background.description").color(Color.white).padBottom(20f); addListener(new InputListener() { private float lastX, lastY; @@ -87,20 +132,42 @@ public boolean scrolled(InputEvent event, float x, float y, float amountX, float } }); - shown(this::setup); + shown(() -> { + savedTexture = state.rules.backgroundTexture; + textureMode = state.rules.planetBackground == null && state.rules.backgroundTexture != null; + setup(); + }); } - private void addBackground() { - rotX = 90f; - rotY = 90f; - zoom = 1f; - state.rules.planetBackground = new PlanetParams(); + private void addPlanetBackground() { + savedTexture = state.rules.backgroundTexture; + state.rules.backgroundTexture = null; + if (state.rules.planetBackground == null) { + rotX = 90f; + rotY = 90f; + zoom = 1f; + state.rules.planetBackground = new PlanetParams(); + } params = state.rules.planetBackground; + textureMode = false; + setup(); + } + + private void addTextureBackground() { + state.rules.planetBackground = null; + state.rules.backgroundTexture = savedTexture != null && !savedTexture.isEmpty() ? savedTexture : ""; + params = null; + textureMode = true; + PlanetBackgroundDrawer.update(); setup(); } private void removeBackground() { state.rules.planetBackground = null; + state.rules.backgroundTexture = null; + savedTexture = null; + params = null; + textureMode = false; PlanetBackgroundDrawer.update(); setup(); } @@ -131,25 +198,332 @@ private void planetDialog() { dialog.show(); } + private static final float CELL_SIZE = 160f; + + private enum TextureTab { SPRITES, CUSTOM } + + private static class TextureEntry { + final String path; + final String name; + TextureRegion region; + + TextureEntry(String path, String name, TextureRegion region) { + this.path = path; + this.name = name; + this.region = region; + } + } + + private Seq cachedSprites, cachedCustom; + + private Seq getSprites() { + if (cachedSprites == null) { + cachedSprites = new Seq<>(); + for (String name : Core.assets.getAssetNames()) { + if (!name.startsWith("sprites/") || !name.endsWith(".png")) continue; + if (!Texture.class.equals(Core.assets.getAssetType(name))) continue; + + Texture tex = Core.assets.get(name, Texture.class); + int slash = name.lastIndexOf('/') + 1; + cachedSprites.add(new TextureEntry(name, name.substring(slash, name.length() - 4), new TextureRegion(tex))); + } + cachedSprites.sort((a, b) -> a.name.compareTo(b.name)); + } + return cachedSprites; + } + + private Seq getCustomTextures() { + if (cachedCustom == null) { + cachedCustom = new Seq<>(); + Fi dir = dataDirectory.child("backgrounds"); + if (dir.exists()) { + for (Fi file : dir.list()) { + if (!file.extEquals("png")) continue; + TextureEntry entry = loadCustomEntry(file); + if (entry != null) cachedCustom.add(entry); + } + } + cachedCustom.sort((a, b) -> a.name.compareTo(b.name)); + } + return cachedCustom; + } + + private Pixmap loadPixmap(byte[] bytes) { + try { + return new Pixmap(bytes); + } catch (Exception ignored) {} + + try { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes)); + if (image == null) return null; + + int w = image.getWidth(), h = image.getHeight(); + Pixmap pixmap = new Pixmap(w, h); + int[] pixels = image.getRGB(0, 0, w, h, null, 0, w); + ByteBuffer buf = pixmap.pixels; + for (int pixel : pixels) { + buf.put((byte)((pixel >> 16) & 0xFF)); + buf.put((byte)((pixel >> 8) & 0xFF)); + buf.put((byte)(pixel & 0xFF)); + buf.put((byte)((pixel >> 24) & 0xFF)); + } + buf.flip(); + return pixmap; + } catch (Exception ignored) {} + + return null; + } + + private TextureEntry loadCustomEntry(Fi file) { + try { + Pixmap pixmap = loadPixmap(file.readBytes()); + if (pixmap == null) return null; + Texture tex = new Texture(pixmap); + tex.setFilter(Texture.TextureFilter.linear); + pixmap.dispose(); + return new TextureEntry(file.absolutePath(), file.nameWithoutExtension(), new TextureRegion(tex)); + } catch (Exception ignored) { + return null; + } + } + + private void saveCustomTexture(Pixmap pixmap, Fi dest, BaseDialog dialog, Runnable rebuild) { + dest.writePng(pixmap); + pixmap.dispose(); + + TextureEntry existing = cachedCustom != null ? cachedCustom.find(e -> e.path.equals(dest.absolutePath())) : null; + if (existing != null) { + if (existing.region != null && existing.region.texture != null) { + existing.region.texture.dispose(); + } + cachedCustom.remove(existing); + } + + TextureEntry entry = loadCustomEntry(dest); + if (entry == null) return; + + if (cachedCustom == null) cachedCustom = new Seq<>(); + cachedCustom.add(entry); + cachedCustom.sort((a, b) -> a.name.compareTo(b.name)); + rebuild.run(); + } + + private void importCustomTexture(BaseDialog dialog, Runnable rebuild) { + platform.showMultiFileChooser(file -> { + Fi dir = dataDirectory.child("backgrounds"); + dir.mkdirs(); + + byte[] bytes = file.readBytes(); + Pixmap pixmap = loadPixmap(bytes); + if (pixmap == null) { + ui.showErrorMessage("@rules.background.importerror"); + return; + } + + String baseName = file.nameWithoutExtension(); + Fi dest = dir.child(baseName + ".png"); + + if (cachedCustom != null && cachedCustom.contains(e -> e.name.equals(baseName))) { + BaseDialog confirm = new BaseDialog("@rules.background.duplicateerror"); + confirm.cont.add("@rules.background.duplicateerror").pad(20f); + confirm.addCloseButton(); + confirm.buttons.button("@rules.background.overwrite", Icon.save, () -> { + saveCustomTexture(pixmap, dest, dialog, rebuild); + confirm.hide(); + }).size(210f, 64f); + confirm.keyDown(KeyCode.escape, confirm::hide); + confirm.show(); + return; + } + + saveCustomTexture(pixmap, dest, dialog, rebuild); + }, "png", "jpg", "jpeg", "bmp", "gif", "tga", "tiff", "tif", "webp"); + } + + private void textureDialog() { + BaseDialog dialog = new BaseDialog("@rules.background.selecttexture"); + Table grid = new Table(); + String[] filter = {""}; + float[] lastWidth = {0f}; + String currentTex = state.rules.backgroundTexture; + boolean isCustom = currentTex != null && !currentTex.isEmpty() && !currentTex.startsWith("sprites/"); + TextureTab[] tab = {isCustom ? TextureTab.CUSTOM : TextureTab.SPRITES}; + String[] selected = {currentTex}; + + Runnable rebuild = () -> rebuildTextureGrid(grid, filter[0], lastWidth[0], tab[0], selected, dialog); + + dialog.cont.table(t -> { + t.table(top -> { + top.table(tabs -> { + ButtonGroup group = new ButtonGroup<>(); + tabs.defaults().width(120f).height(40f); + tabs.button("Sprites", Styles.flatTogglet, () -> { tab[0] = TextureTab.SPRITES; rebuild.run(); }) + .group(group).checked(!isCustom).row(); + tabs.button("Custom", Styles.flatTogglet, () -> { tab[0] = TextureTab.CUSTOM; rebuild.run(); }) + .group(group).checked(isCustom).row(); + }).left().top().padRight(10f); + + top.field("", text -> { + filter[0] = text; + rebuild.run(); + }).grow(); + }).growX().row(); + + ScrollPane scroll = new ScrollPane(grid); + scroll.update(() -> { + float w = scroll.getWidth(); + if (w > 0f && Math.abs(w - lastWidth[0]) > 10f) { + lastWidth[0] = w; + rebuild.run(); + } + }); + t.add(scroll).grow(); + }).grow(); + + rebuild.run(); + dialog.addCloseButton(); + dialog.buttons.button("@rules.background.apply", Icon.ok, () -> { + if (selected[0] != null && !selected[0].isEmpty()) { + state.rules.backgroundTexture = selected[0]; + savedTexture = selected[0]; + PlanetBackgroundDrawer.update(); + dialog.hide(); + setup(); + } + }).size(210f, 64f); + dialog.buttons.button("@rules.background.importcustom", Icon.upload, + () -> importCustomTexture(dialog, rebuild)).size(210f, 64f); + dialog.buttons.button("@rules.background.deletetexture", Icon.trash, () -> { + if (tab[0] != TextureTab.CUSTOM || selected[0] == null) return; + if (cachedCustom == null) return; + + TextureEntry found = cachedCustom.find(e -> e.path.equals(selected[0])); + if (found == null) return; + + ui.showConfirm("@rules.background.deleteconfirm", () -> { + new Fi(found.path).delete(); + cachedCustom.remove(found); + + if (selected[0].equals(found.path)) selected[0] = null; + if (found.path.equals(state.rules.backgroundTexture)) { + state.rules.backgroundTexture = ""; + setup(); + } + rebuild.run(); + }); + }).size(210f, 64f).update(b -> b.setDisabled(tab[0] != TextureTab.CUSTOM)); + dialog.keyDown(KeyCode.escape, dialog::hide); + dialog.show(); + } + + private Seq getEntriesForTab(TextureTab tab) { + switch (tab) { + case SPRITES: return getSprites(); + case CUSTOM: return getCustomTextures(); + default: return new Seq<>(); + } + } + + private void rebuildTextureGrid(Table grid, String filter, float rawWidth, TextureTab tab, String[] selected, BaseDialog dialog) { + grid.clear(); + + float availWidth = rawWidth <= 0f ? Core.graphics.getWidth() / Scl.scl() - 100f : rawWidth; + int cols = Math.max(2, (int)(availWidth / CELL_SIZE)); + int i = 0; + + for (TextureEntry entry : getEntriesForTab(tab)) { + if (!filter.isEmpty() && !entry.name.contains(filter)) continue; + if (entry.region == null) continue; + + boolean isSel = entry.path.equals(selected[0]); + + ImageButton.ImageButtonStyle style = new ImageButton.ImageButtonStyle(Styles.clearNoneTogglei); + style.imageUp = new TextureRegionDrawable(entry.region); + ImageButton button = new ImageButton(style); + button.resizeImage(128f); + button.setChecked(isSel); + button.clicked(() -> { + selected[0] = entry.path; + rebuildTextureGrid(grid, filter, availWidth, tab, selected, dialog); + }); + + grid.table(cell -> { + cell.add(button).size(140f).row(); + cell.add(entry.name).width(CELL_SIZE - 10f).fontScale(0.7f).wrap().center() + .color(isSel ? Pal.accent : Color.lightGray); + }).pad(4f).top().width(CELL_SIZE); + + if (++i >= cols) { + i = 0; + grid.row(); + } + } + } + + private TextureRegion getTexturePreview(String path) { + if (path == null || path.isEmpty()) return null; + + if (previewTexture != null) { + previewTexture.dispose(); + previewTexture = null; + } + + if (path.startsWith("sprites/") && Core.assets.isLoaded(path, Texture.class)) { + return new TextureRegion(Core.assets.get(path, Texture.class)); + } + + Fi file = new Fi(path); + if (file.exists()) { + Pixmap pixmap = loadPixmap(file.readBytes()); + if (pixmap != null) { + previewTexture = new Texture(pixmap); + previewTexture.setFilter(Texture.TextureFilter.linear); + pixmap.dispose(); + return new TextureRegion(previewTexture); + } + } + + return null; + } + + private boolean hasBackground() { + return state.rules.planetBackground != null || + (state.rules.backgroundTexture != null && !state.rules.backgroundTexture.isEmpty()); + } + private void setup() { params = state.rules.planetBackground; if (params != null) { + textureMode = false; zoom = params.zoom; if (params.camPos != null && !params.camPos.isZero()) { Vec3 dir = params.camPos.cpy().nor(); rotY = (float)Math.acos(dir.y) * Mathf.radDeg; rotX = Mathf.mod(Mathf.atan2(dir.z, dir.x) * Mathf.radDeg, 360f); } + } else if (state.rules.backgroundTexture != null) { + textureMode = true; } + descriptionRow.visible = !textureMode; + cont.clear(); cont.table(t -> main = t); - if (params == null) { + if (!hasBackground() && !textureMode) { main.add("@rules.background.nobackground").color(Color.white).padBottom(20f).row(); - main.button("@rules.background.addbackground", Styles.togglet, this::addBackground) - .marginLeft(14f).width(220f).height(55f); + } + + if (textureMode) { + String current = state.rules.backgroundTexture; + if (current != null && !current.isEmpty()) { + TextureRegion preview = getTexturePreview(current); + if (preview != null) { + main.add(new arc.scene.ui.Image(preview)).size(256f).pad(10f).row(); + } + main.add(current).color(Color.lightGray).fontScale(0.8f).padTop(5f).row(); + } } updateRotation(); diff --git a/src/main/resources/bundles/bundle.properties b/src/main/resources/bundles/bundle.properties index 676b38a..6fa1c22 100644 --- a/src/main/resources/bundles/bundle.properties +++ b/src/main/resources/bundles/bundle.properties @@ -152,10 +152,22 @@ plotstate.ghost = Game pretends there's a plot there settings.mindurka = Mindurka -rules.planetbackground = Planet Background +rules.planetbackground = World Background rules.background.selectplanet = Select Planet rules.background.description = Right-click drag to rotate. Scroll to zoom. rules.background.nobackground = No background set rules.background.addbackground = Add Background rules.background.removebackground = Remove Background -rules.background.removalwarning = Are you sure you want to remove the planet background? \ No newline at end of file +rules.background.removalwarning = Are you sure you want to remove the background? +rules.background.mode.planet = Planet +rules.background.mode.texture = Texture +rules.background.texturepath = Texture path +rules.background.selecttexture = Select Texture +rules.background.selected = Selected texture: +rules.background.importcustom = Import from file +rules.background.importerror = Failed to load image. File may be corrupt or unsupported format. +rules.background.duplicateerror = A texture with this name already exists. +rules.background.apply = Apply +rules.background.overwrite = Overwrite +rules.background.deletetexture = Delete Texture +rules.background.deleteconfirm = Are you sure you want to delete this texture? \ No newline at end of file diff --git a/src/main/resources/bundles/bundle_ru.properties b/src/main/resources/bundles/bundle_ru.properties index b187f16..790541c 100644 --- a/src/main/resources/bundles/bundle_ru.properties +++ b/src/main/resources/bundles/bundle_ru.properties @@ -136,10 +136,22 @@ plotstate.locked = Плот существует пока команда не у plotstate.static = Статический плот (Плот не может быть уничтожен/потоплен) plotstate.ghost = Призрачный плот (Статичексий плот без его схематики) -rules.planetbackground = Фон планеты +rules.planetbackground = Фон мира rules.background.selectplanet = Выбрать планету rules.background.description = ПКМ + перетащить для вращения. Колёсико мыши — масштаб. rules.background.nobackground = Фон не задан rules.background.addbackground = Добавить фон rules.background.removebackground = Удалить фон -rules.background.removalwarning = Вы уверены, что хотите удалить фон планеты? +rules.background.removalwarning = Вы уверены, что хотите удалить фон? +rules.background.mode.planet = Планета +rules.background.mode.texture = Текстура +rules.background.texturepath = Путь к текстуре +rules.background.selecttexture = Выбрать текстуру +rules.background.selected = Выбранная текстура: +rules.background.importcustom = Импорт из файла +rules.background.importerror = Не удалось загрузить изображение. Файл повреждён или формат не поддерживается. +rules.background.duplicateerror = Текстура с таким именем уже существует. +rules.background.apply = Применить +rules.background.overwrite = Перезаписать +rules.background.deletetexture = Удалить текстуру +rules.background.deleteconfirm = Вы уверены, что хотите удалить эту текстуру?