Skip to content

Commit ac20dff

Browse files
authored
improve collection settings' imgui implementation (#285)
1 parent c0299a1 commit ac20dff

File tree

7 files changed

+90
-33
lines changed

7 files changed

+90
-33
lines changed

src/main/kotlin/com/lambda/config/groups/BreakSettings.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.lambda.interaction.managers.breaking.BreakConfig.BreakMode
2929
import com.lambda.interaction.managers.breaking.BreakConfig.SwingMode
3030
import com.lambda.util.NamedEnum
3131
import net.minecraft.block.Block
32+
import net.minecraft.registry.Registries
3233
import java.awt.Color
3334

3435
open class BreakSettings(
@@ -77,7 +78,7 @@ open class BreakSettings(
7778
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()
7879

7980
// Block
80-
override val ignoredBlocks by c.setting("${prefix}Ignored Blocks", emptySet<Block>(), description = "Blocks that wont be broken", visibility = visibility).group(*baseGroup, Group.General).index()
81+
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()
8182
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()
8283
override val avoidSupporting by c.setting("${prefix}Avoid Supporting", true, "Avoids breaking the block supporting the player", visibility = visibility).group(*baseGroup, Group.General).index()
8384
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()

src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ class BlockCollectionSetting(
3333
serialize = true,
3434
) {
3535
context(setting: Setting<*, MutableCollection<Block>>)
36-
override fun ImGuiBuilder.buildLayout() = buildComboBox("block") { BlockCodec.stringify(it) }
36+
override fun ImGuiBuilder.buildLayout() = buildDualPane("block") { BlockCodec.stringify(it) }
3737
}

src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ClassCollectionSetting<T : Any>(
3838
serialize = false,
3939
) {
4040
context(setting: Setting<*, MutableCollection<T>>)
41-
override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { it.className }
41+
override fun ImGuiBuilder.buildLayout() = buildDualPane("item") { it.className }
4242

4343
// When serializing the list to json we do not want to serialize the elements' classes, but their stringified representation.
4444
// If we do serialize the classes we'll run into missing type adapters errors by Gson.

src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@ import com.lambda.config.SettingEditorDsl
2626
import com.lambda.config.SettingGroupEditor
2727
import com.lambda.context.SafeContext
2828
import com.lambda.gui.dsl.ImGuiBuilder
29+
import com.lambda.imgui.ImGui
30+
import com.lambda.imgui.ImGui.getContentRegionAvail
2931
import com.lambda.threading.runSafe
3032
import com.lambda.imgui.ImGuiListClipper
3133
import com.lambda.imgui.callback.ImListClipperCallback
3234
import com.lambda.imgui.flag.ImGuiChildFlags
35+
import com.lambda.imgui.flag.ImGuiPopupFlags
3336
import com.lambda.imgui.flag.ImGuiSelectableFlags.DontClosePopups
3437
import java.lang.reflect.Type
3538

@@ -66,47 +69,100 @@ open class CollectionSetting<R : Any>(
6669
val deselectListeners = mutableListOf<SafeContext.(R) -> Unit>()
6770

6871
context(setting: Setting<*, MutableCollection<R>>)
69-
override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { it.toString() }
72+
override fun ImGuiBuilder.buildLayout() = buildDualPane("item") { it.toString() }
7073

7174
context(setting: Setting<*, MutableCollection<R>>)
72-
fun ImGuiBuilder.buildComboBox(itemName: String, toString: (R) -> String) {
75+
fun ImGuiBuilder.buildDualPane(itemName: String, toString: (R) -> String) {
7376
val text = if (value.size == 1) itemName else "${itemName}s"
77+
val popupId = "##${setting.name}-collection-popup"
7478

75-
combo("##${setting.name}", "${setting.name}: ${value.size} $text") {
79+
button("${setting.name}: ${value.size} $text") {
80+
ImGui.openPopup(popupId)
81+
}
82+
83+
ImGui.setNextWindowSizeConstraints(500f, 0f, Float.MAX_VALUE, io.displaySize.y * 0.5f)
84+
popupContextItem(popupId, ImGuiPopupFlags.None) {
7685
inputText("##${setting.name}-SearchBox", ::searchFilter)
7786

78-
child(
79-
strId = "##${setting.name}-ComboOptionsChild",
80-
childFlags = ImGuiChildFlags.AutoResizeY or ImGuiChildFlags.AlwaysAutoResize,
81-
) {
82-
val list = immutableCollection
83-
.filter { item ->
84-
val q = searchFilter.trim()
85-
if (q.isEmpty()) true
86-
else toString(item).contains(q, ignoreCase = true)
87+
val q = searchFilter.trim()
88+
val filteredDeselected = immutableCollection
89+
.filter { item -> !value.contains(item) && (q.isEmpty() || toString(item).contains(q, ignoreCase = true)) }
90+
val filteredSelected = immutableCollection
91+
.filter { item -> value.contains(item) && (q.isEmpty() || toString(item).contains(q, ignoreCase = true)) }
92+
93+
val availableWidth = getContentRegionAvail().x
94+
val swapButtonWidth = 30f
95+
val paneWidth = (availableWidth - swapButtonWidth - style.itemSpacing.x * 2) / 2f
96+
val paneHeight = 200f
97+
98+
group {
99+
textDisabled("Deselected (${filteredDeselected.size})")
100+
child(
101+
strId = "##${setting.name}-Deselected",
102+
width = paneWidth,
103+
height = paneHeight,
104+
childFlags = ImGuiChildFlags.Border or ImGuiChildFlags.ResizeX or ImGuiChildFlags.ResizeY,
105+
) {
106+
val deselectedCallback = object : ImListClipperCallback() {
107+
override fun accept(index: Int) {
108+
val v = filteredDeselected.getOrNull(index) ?: return
109+
selectable(
110+
label = toString(v),
111+
flags = DontClosePopups
112+
) {
113+
value.add(v)
114+
runSafe { selectListeners.forEach { listener -> listener(v) } }
115+
}
116+
}
117+
}
118+
ImGuiListClipper.forEach(filteredDeselected.size, deselectedCallback)
119+
}
120+
}
121+
122+
sameLine()
123+
124+
group {
125+
cursorPosY += paneHeight / 2f
126+
button("<>", swapButtonWidth) {
127+
val currentlySelected = value.toList()
128+
val allItems = immutableCollection.toList()
129+
value.clear()
130+
allItems.forEach { item ->
131+
if (!currentlySelected.contains(item)) {
132+
value.add(item)
133+
}
134+
}
135+
runSafe {
136+
currentlySelected.forEach { v -> deselectListeners.forEach { listener -> listener(v) } }
137+
value.forEach { v -> selectListeners.forEach { listener -> listener(v) } }
87138
}
139+
}
140+
}
88141

89-
val listClipperCallback = object : ImListClipperCallback() {
90-
override fun accept(index: Int) {
91-
val v = list.getOrNull(index) ?: return
92-
val selected = value.contains(v)
93-
94-
selectable(
95-
label = toString(v),
96-
selected = selected,
97-
flags = DontClosePopups
98-
) {
99-
if (selected) {
142+
sameLine()
143+
144+
group {
145+
textDisabled("Selected (${filteredSelected.size})")
146+
child(
147+
strId = "##${setting.name}-Selected",
148+
width = paneWidth,
149+
height = paneHeight,
150+
childFlags = ImGuiChildFlags.Border or ImGuiChildFlags.ResizeX or ImGuiChildFlags.ResizeY,
151+
) {
152+
val selectedCallback = object : ImListClipperCallback() {
153+
override fun accept(index: Int) {
154+
val v = filteredSelected.getOrNull(index) ?: return
155+
selectable(
156+
label = toString(v),
157+
flags = DontClosePopups
158+
) {
100159
value.remove(v)
101160
runSafe { deselectListeners.forEach { listener -> listener(v) } }
102-
} else {
103-
value.add(v)
104-
runSafe { selectListeners.forEach { listener -> listener(v) } }
105161
}
106162
}
107163
}
164+
ImGuiListClipper.forEach(filteredSelected.size, selectedCallback)
108165
}
109-
ImGuiListClipper.forEach(list.size, listClipperCallback)
110166
}
111167
}
112168
}

src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ class ItemCollectionSetting(
3333
serialize = true,
3434
) {
3535
context(setting: Setting<*, MutableCollection<Item>>)
36-
override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { ItemCodec.stringify(it) }
36+
override fun ImGuiBuilder.buildLayout() = buildDualPane("item") { ItemCodec.stringify(it) }
3737
}

src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object BasicChecker : Results<PreSimResult> {
4848
}
4949

5050
// block should be ignored
51-
if (state.block in breakConfig.ignoredBlocks && this@hasBasicRequirements is BreakSimInfo) {
51+
if (this@hasBasicRequirements is BreakSimInfo && state.block !in breakConfig.blocks) {
5252
result(GenericResult.Ignored(pos))
5353
return false
5454
}

src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface BreakConfig : ActionConfig, ISettingGroup {
5353
val avoidFluids: Boolean
5454
val fillFluids: Boolean
5555
val avoidSupporting: Boolean
56-
val ignoredBlocks: Collection<Block>
56+
val blocks: Collection<Block>
5757

5858
val efficientOnly: Boolean
5959
val suitableToolsOnly: Boolean

0 commit comments

Comments
 (0)