From ad5c6408f26b1aac272122979bede148ab741998 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:43:34 +0100 Subject: [PATCH 1/2] better collection setting imgui implementation --- .../collections/BlockCollectionSetting.kt | 2 +- .../collections/ClassCollectionSetting.kt | 2 +- .../settings/collections/CollectionSetting.kt | 110 +++++++++++++----- .../collections/ItemCollectionSetting.kt | 2 +- 4 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt index 7e8e66f2a..62452f9f3 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt @@ -33,5 +33,5 @@ class BlockCollectionSetting( serialize = true, ) { context(setting: Setting<*, MutableCollection>) - override fun ImGuiBuilder.buildLayout() = buildComboBox("block") { BlockCodec.stringify(it) } + override fun ImGuiBuilder.buildLayout() = buildDualPane("block") { BlockCodec.stringify(it) } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt index f1064a061..f40ef8ff3 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt @@ -38,7 +38,7 @@ class ClassCollectionSetting( serialize = false, ) { context(setting: Setting<*, MutableCollection>) - override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { it.className } + override fun ImGuiBuilder.buildLayout() = buildDualPane("item") { it.className } // When serializing the list to json we do not want to serialize the elements' classes, but their stringified representation. // If we do serialize the classes we'll run into missing type adapters errors by Gson. diff --git a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt index 0817a515e..b8d1a310a 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt @@ -26,10 +26,13 @@ import com.lambda.config.SettingEditorDsl import com.lambda.config.SettingGroupEditor import com.lambda.context.SafeContext import com.lambda.gui.dsl.ImGuiBuilder +import com.lambda.imgui.ImGui +import com.lambda.imgui.ImGui.getContentRegionAvail import com.lambda.threading.runSafe import com.lambda.imgui.ImGuiListClipper import com.lambda.imgui.callback.ImListClipperCallback import com.lambda.imgui.flag.ImGuiChildFlags +import com.lambda.imgui.flag.ImGuiPopupFlags import com.lambda.imgui.flag.ImGuiSelectableFlags.DontClosePopups import java.lang.reflect.Type @@ -66,47 +69,100 @@ open class CollectionSetting( val deselectListeners = mutableListOf Unit>() context(setting: Setting<*, MutableCollection>) - override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { it.toString() } + override fun ImGuiBuilder.buildLayout() = buildDualPane("item") { it.toString() } context(setting: Setting<*, MutableCollection>) - fun ImGuiBuilder.buildComboBox(itemName: String, toString: (R) -> String) { + fun ImGuiBuilder.buildDualPane(itemName: String, toString: (R) -> String) { val text = if (value.size == 1) itemName else "${itemName}s" + val popupId = "##${setting.name}-collection-popup" - combo("##${setting.name}", "${setting.name}: ${value.size} $text") { + button("${setting.name}: ${value.size} $text") { + ImGui.openPopup(popupId) + } + + ImGui.setNextWindowSizeConstraints(500f, 0f, Float.MAX_VALUE, io.displaySize.y * 0.5f) + popupContextItem(popupId, ImGuiPopupFlags.None) { inputText("##${setting.name}-SearchBox", ::searchFilter) - child( - strId = "##${setting.name}-ComboOptionsChild", - childFlags = ImGuiChildFlags.AutoResizeY or ImGuiChildFlags.AlwaysAutoResize, - ) { - val list = immutableCollection - .filter { item -> - val q = searchFilter.trim() - if (q.isEmpty()) true - else toString(item).contains(q, ignoreCase = true) + val q = searchFilter.trim() + val filteredDeselected = immutableCollection + .filter { item -> !value.contains(item) && (q.isEmpty() || toString(item).contains(q, ignoreCase = true)) } + val filteredSelected = immutableCollection + .filter { item -> value.contains(item) && (q.isEmpty() || toString(item).contains(q, ignoreCase = true)) } + + val availableWidth = getContentRegionAvail().x + val swapButtonWidth = 30f + val paneWidth = (availableWidth - swapButtonWidth - style.itemSpacing.x * 2) / 2f + val paneHeight = 200f + + group { + textDisabled("Deselected (${filteredDeselected.size})") + child( + strId = "##${setting.name}-Deselected", + width = paneWidth, + height = paneHeight, + childFlags = ImGuiChildFlags.Border or ImGuiChildFlags.ResizeX or ImGuiChildFlags.ResizeY, + ) { + val deselectedCallback = object : ImListClipperCallback() { + override fun accept(index: Int) { + val v = filteredDeselected.getOrNull(index) ?: return + selectable( + label = toString(v), + flags = DontClosePopups + ) { + value.add(v) + runSafe { selectListeners.forEach { listener -> listener(v) } } + } + } + } + ImGuiListClipper.forEach(filteredDeselected.size, deselectedCallback) + } + } + + sameLine() + + group { + cursorPosY += paneHeight / 2f + button("<>", swapButtonWidth) { + val currentlySelected = value.toList() + val allItems = immutableCollection.toList() + value.clear() + allItems.forEach { item -> + if (!currentlySelected.contains(item)) { + value.add(item) + } + } + runSafe { + currentlySelected.forEach { v -> deselectListeners.forEach { listener -> listener(v) } } + value.forEach { v -> selectListeners.forEach { listener -> listener(v) } } } + } + } - val listClipperCallback = object : ImListClipperCallback() { - override fun accept(index: Int) { - val v = list.getOrNull(index) ?: return - val selected = value.contains(v) - - selectable( - label = toString(v), - selected = selected, - flags = DontClosePopups - ) { - if (selected) { + sameLine() + + group { + textDisabled("Selected (${filteredSelected.size})") + child( + strId = "##${setting.name}-Selected", + width = paneWidth, + height = paneHeight, + childFlags = ImGuiChildFlags.Border or ImGuiChildFlags.ResizeX or ImGuiChildFlags.ResizeY, + ) { + val selectedCallback = object : ImListClipperCallback() { + override fun accept(index: Int) { + val v = filteredSelected.getOrNull(index) ?: return + selectable( + label = toString(v), + flags = DontClosePopups + ) { value.remove(v) runSafe { deselectListeners.forEach { listener -> listener(v) } } - } else { - value.add(v) - runSafe { selectListeners.forEach { listener -> listener(v) } } } } } + ImGuiListClipper.forEach(filteredSelected.size, selectedCallback) } - ImGuiListClipper.forEach(list.size, listClipperCallback) } } } diff --git a/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt index b58639662..8a045ed29 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt @@ -33,5 +33,5 @@ class ItemCollectionSetting( serialize = true, ) { context(setting: Setting<*, MutableCollection>) - override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { ItemCodec.stringify(it) } + override fun ImGuiBuilder.buildLayout() = buildDualPane("item") { ItemCodec.stringify(it) } } \ No newline at end of file From 57872369fd9f179207e883bf4f4a98d3ab46bbe3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:54:50 +0100 Subject: [PATCH 2/2] block whitelist / blacklist in the break settings --- src/main/kotlin/com/lambda/config/groups/BreakSettings.kt | 3 ++- .../interaction/construction/simulation/checks/BasicChecker.kt | 2 +- .../com/lambda/interaction/managers/breaking/BreakConfig.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt index c8ad126e2..2af037210 100644 --- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt @@ -29,6 +29,7 @@ import com.lambda.interaction.managers.breaking.BreakConfig.BreakMode import com.lambda.interaction.managers.breaking.BreakConfig.SwingMode import com.lambda.util.NamedEnum import net.minecraft.block.Block +import net.minecraft.registry.Registries import java.awt.Color open class BreakSettings( @@ -77,7 +78,7 @@ open class BreakSettings( override val breaksPerTick by c.setting("${prefix}Breaks Per Tick", 30, 1..30, 1, "Maximum instant block breaks per tick", visibility = visibility).group(*baseGroup, Group.General).index() // Block - override val ignoredBlocks by c.setting("${prefix}Ignored Blocks", emptySet(), description = "Blocks that wont be broken", visibility = visibility).group(*baseGroup, Group.General).index() + override val blocks by c.setting("${prefix}Blocks", Registries.BLOCK.toList(), description = "Blocks that are allowed to be broken", visibility = visibility).group(*baseGroup, Group.General).index() override val avoidFluids by c.setting("${prefix}Avoid Fluids", true, "Avoids breaking blocks that would cause fluids to spill", visibility = visibility).group(*baseGroup, Group.General).index() override val avoidSupporting by c.setting("${prefix}Avoid Supporting", true, "Avoids breaking the block supporting the player", visibility = visibility).group(*baseGroup, Group.General).index() override val fillFluids by c.setting("Fill Fluids", true, "Fills fluids in order to break blocks that would initially spill them") { visibility() && avoidFluids }.group(*baseGroup, Group.General).index() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt index 5c96bbdf7..880197617 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt @@ -48,7 +48,7 @@ object BasicChecker : Results { } // block should be ignored - if (state.block in breakConfig.ignoredBlocks && this@hasBasicRequirements is BreakSimInfo) { + if (this@hasBasicRequirements is BreakSimInfo && state.block !in breakConfig.blocks) { result(GenericResult.Ignored(pos)) return false } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt index 9aa8eb7c5..0581ea1b7 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt @@ -53,7 +53,7 @@ interface BreakConfig : ActionConfig, ISettingGroup { val avoidFluids: Boolean val fillFluids: Boolean val avoidSupporting: Boolean - val ignoredBlocks: Collection + val blocks: Collection val efficientOnly: Boolean val suitableToolsOnly: Boolean