Skip to content
This repository was archived by the owner on Dec 30, 2022. It is now read-only.

Commit 2e7d122

Browse files
authored
Merge pull request #90 from DeathsGun/features/79_browsing_improvments
Better mod browsing by making categories multiselectable
2 parents 63747ab + d0b6688 commit 2e7d122

File tree

12 files changed

+180
-71
lines changed

12 files changed

+180
-71
lines changed

src/main/kotlin/xyz/deathsgun/modmanager/api/gui/list/IListScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ interface IListScreen {
2424

2525
fun <E> updateSelectedEntry(widget: Any, entry: E?)
2626

27+
fun <E> updateMultipleEntries(widget: Any, entries: ArrayList<E>)
28+
2729
}

src/main/kotlin/xyz/deathsgun/modmanager/api/gui/list/ListWidget.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ abstract class ListWidget<E : ListWidget.Entry<E>>(
4141
top: Int,
4242
var bottom: Int,
4343
itemHeight: Int,
44-
private val parent: IListScreen
44+
protected val parent: IListScreen
4545
) : AlwaysSelectedEntryListWidget<E>(client, width, height, top, bottom, itemHeight) {
4646

4747
var renderOutline: Boolean = true
@@ -141,7 +141,7 @@ abstract class ListWidget<E : ListWidget.Entry<E>>(
141141
super.appendNarrations(builder)
142142
}
143143

144-
fun isSelectedEntry(entry: Entry<E>): Boolean {
144+
open fun isSelectedEntry(entry: Entry<E>): Boolean {
145145
return selectedId == entry.id
146146
}
147147

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2021 DeathsGun
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.deathsgun.modmanager.api.gui.list
18+
19+
import kotlinx.serialization.ExperimentalSerializationApi
20+
import net.minecraft.client.MinecraftClient
21+
22+
/**
23+
* Using ModMenu's implementation because the implementation from
24+
* Mojang is broken. [com.terraformersmc.modmenu.gui.ModsScreen].
25+
* All credits for this code go to the Terraformer's
26+
*/
27+
abstract class MultiSelectListWidget<E : MultiSelectListWidget.Entry<E>>(
28+
client: MinecraftClient,
29+
width: Int,
30+
height: Int,
31+
top: Int,
32+
bottom: Int,
33+
itemHeight: Int,
34+
parent: IListScreen
35+
) : ListWidget<E>(client, width, height, top, bottom, itemHeight, parent) {
36+
37+
private var selectedIds = ArrayList<String>()
38+
39+
@OptIn(ExperimentalSerializationApi::class)
40+
override fun setSelected(entry: E?) {
41+
super.setSelected(entry)
42+
if (entry == null) {
43+
return
44+
}
45+
if (selectedIds.contains(entry.id)) {
46+
selectedIds.removeIf { it == entry.id }
47+
} else {
48+
selectedIds.add(entry.id)
49+
}
50+
parent.updateMultipleEntries(
51+
this,
52+
ArrayList(children().filter { selectedIds.contains(it.id) }.sortedBy { selectedIds.indexOf(it.id) })
53+
)
54+
}
55+
56+
fun setSelected(entries: List<E>) {
57+
selectedIds.clear()
58+
for (entry in entries) {
59+
setSelected(entry)
60+
}
61+
}
62+
63+
override fun isSelectedEntry(index: Int): Boolean {
64+
return selectedIds.contains(getEntry(index).id)
65+
}
66+
67+
override fun addEntry(entry: E): Int {
68+
val i = super.addEntry(entry)
69+
if (selectedIds.contains(entry.id)) {
70+
setSelected(entry)
71+
}
72+
return i
73+
}
74+
75+
override fun isSelectedEntry(entry: ListWidget.Entry<E>): Boolean {
76+
return selectedIds.contains(entry.id)
77+
}
78+
79+
abstract class Entry<E : Entry<E>>(list: MultiSelectListWidget<E>, id: String) :
80+
ListWidget.Entry<E>(list, id) {
81+
@Suppress("UNCHECKED_CAST")
82+
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
83+
list.setSelected(this as E)
84+
return true
85+
}
86+
}
87+
}

src/main/kotlin/xyz/deathsgun/modmanager/api/provider/IModProvider.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,24 @@ interface IModProvider : IModUpdateProvider {
4545
/**
4646
* Returns a limited number of [Mod]'s from the specified category
4747
*
48-
* @param category the category of all the mods
49-
* @param page the requested from the UI starting at 1
50-
* @param limit to not overfill the ui and for shorter loading times the amount of returned mods needs to limited
48+
* @param categories the categories of the mods
49+
* @param sorting the sorting order
50+
* @param page the requested from the UI starting at 1
51+
* @param limit to not overfill the ui and for shorter loading times the amount of returned mods needs to limited
5152
* @return a list of sorted mods
5253
*/
53-
fun getMods(category: Category, sorting: Sorting, page: Int, limit: Int): ModsResult
54+
fun getMods(categories: List<Category>, sorting: Sorting, page: Int, limit: Int): ModsResult
5455

5556
/**
5657
* Returns a limited number of [Mod]'s from a given search.
5758
*
5859
* @param query the search string
60+
* @param categories the categories in which should be searched
5961
* @param page the current requested page starts at 0
6062
* @param limit the amount of mods to return
6163
* @return a list of mods matching the search term
6264
*/
63-
fun search(query: String, sorting: Sorting, page: Int, limit: Int): ModsResult
65+
fun search(query: String, categories: List<Category>, sorting: Sorting, page: Int, limit: Int): ModsResult
6466

6567
/**
6668
* Returns a more detailed representation of the mod

src/main/kotlin/xyz/deathsgun/modmanager/gui/ModDetailScreen.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,7 @@ class ModDetailScreen(private val previousScreen: Screen, var mod: Mod) : Screen
185185
override fun <E> updateSelectedEntry(widget: Any, entry: E?) {
186186
}
187187

188+
override fun <E> updateMultipleEntries(widget: Any, entries: ArrayList<E>) {
189+
}
190+
188191
}

src/main/kotlin/xyz/deathsgun/modmanager/gui/ModUpdateInfoScreen.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,7 @@ class ModUpdateInfoScreen(private val previousScreen: Screen, private val update
142142
override fun <E> updateSelectedEntry(widget: Any, entry: E?) {
143143
}
144144

145+
override fun <E> updateMultipleEntries(widget: Any, entries: ArrayList<E>) {
146+
}
147+
145148
}

src/main/kotlin/xyz/deathsgun/modmanager/gui/ModsOverviewScreen.kt

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class ModsOverviewScreen(private val previousScreen: Screen) : Screen(Translatab
4646

4747
private var query: String = ""
4848
private var selectedMod: ModListEntry? = null
49-
private var selectedCategory: CategoryListEntry? = null
49+
private var selectedCategories: ArrayList<CategoryListEntry> = ArrayList()
5050
private var page: Int = 0
5151
private var limit: Int = 20
5252
private var scrollPercentage: Double = 0.0
@@ -137,13 +137,13 @@ class ModsOverviewScreen(private val previousScreen: Screen) : Screen(Translatab
137137
modList.scrollAmount = scrollPercentage
138138
}
139139
}
140-
if (selectedCategory != null) {
141-
if (selectedCategory!!.id == "updatable" && ModManager.modManager.update.updates.isEmpty()) {
140+
if (selectedCategories.isNotEmpty()) {
141+
if (selectedCategories.any { it.category.id == "updatable" } && ModManager.modManager.update.updates.isEmpty()) {
142142
categoryList.setSelectedByIndex(0)
143143
showModsByCategory()
144144
return@launch
145145
}
146-
categoryList.setSelected(selectedCategory)
146+
categoryList.setSelected(selectedCategories)
147147
showModsByCategory()
148148
modList.scrollAmount = scrollPercentage
149149
return@launch
@@ -152,7 +152,7 @@ class ModsOverviewScreen(private val previousScreen: Screen) : Screen(Translatab
152152
showModsBySearch()
153153
return@launch
154154
}
155-
categoryList.setSelectedByIndex(0)
155+
showModsByCategory()
156156
}
157157
modList.init()
158158
categoryList.init()
@@ -178,38 +178,20 @@ class ModsOverviewScreen(private val previousScreen: Screen) : Screen(Translatab
178178
showModsBySearch()
179179
return
180180
}
181-
if (selectedCategory == null) {
182-
showModsByRelevance()
183-
return
184-
}
185181
showModsByCategory()
186182
}
187183

188-
private fun showModsByRelevance() {
189-
val provider = ModManager.modManager.getSelectedProvider() ?: return
190-
when (val result = provider.getMods(Sorting.RELEVANCE, page, limit)) {
191-
is ModsResult.Error -> {
192-
error = result.text
193-
}
194-
is ModsResult.Success -> {
195-
error = null
196-
modList.setMods(result.mods)
197-
}
198-
}
199-
}
200-
201184
private fun showModsByCategory() {
202-
selectedCategory ?: return
203185
query = ""
204186
val provider = ModManager.modManager.getSelectedProvider() ?: return
205-
if (selectedCategory!!.id == "updatable") {
187+
if (selectedCategories.any { it.id == "updatable" }) {
206188
modList.clear()
207189
ModManager.modManager.update.updates.forEach {
208190
modList.add(it.mod)
209191
}
210192
return
211193
}
212-
when (val result = provider.getMods(selectedCategory!!.category, sorting, page, limit)) {
194+
when (val result = provider.getMods(selectedCategories.map { it.category }, sorting, page, limit)) {
213195
is ModsResult.Error -> {
214196
error = result.text
215197
}
@@ -232,7 +214,7 @@ class ModsOverviewScreen(private val previousScreen: Screen) : Screen(Translatab
232214

233215
private fun showModsBySearch() {
234216
val provider = ModManager.modManager.getSelectedProvider() ?: return
235-
when (val result = provider.search(this.query, sorting, page, limit)) {
217+
when (val result = provider.search(this.query, selectedCategories.map { it.category }, sorting, page, limit)) {
236218
is ModsResult.Error -> {
237219
this.error = result.text
238220
}
@@ -248,32 +230,44 @@ class ModsOverviewScreen(private val previousScreen: Screen) : Screen(Translatab
248230
}
249231

250232
override fun <E> updateSelectedEntry(widget: Any, entry: E?) {
251-
if (widget is ModListWidget) {
252-
if (entry == null) {
253-
return
254-
}
255-
if (selectedMod == entry) {
256-
if (selectedCategory?.id == "updatable" && query.isEmpty()) {
257-
val update = ModManager.modManager.update.getUpdateForMod(selectedMod!!.mod) ?: return
258-
client?.setScreen(ModUpdateInfoScreen(this, update))
259-
return
260-
}
261-
client?.setScreen(ModDetailScreen(this, selectedMod!!.mod))
233+
if (widget !is ModListWidget) {
234+
return
235+
}
236+
if (entry == null) {
237+
return
238+
}
239+
if (selectedMod == entry) {
240+
if (selectedCategories.any { it.id == "updatable" } && query.isEmpty()) {
241+
val update = ModManager.modManager.update.getUpdateForMod(selectedMod!!.mod) ?: return
242+
client?.setScreen(ModUpdateInfoScreen(this, update))
262243
return
263244
}
264-
selectedMod = entry as ModListEntry
245+
client?.setScreen(ModDetailScreen(this, selectedMod!!.mod))
265246
return
266247
}
267-
if (widget is CategoryListWidget) {
268-
if (entry == null) {
269-
return
248+
selectedMod = entry as ModListEntry
249+
}
250+
251+
override fun <E> updateMultipleEntries(widget: Any, entries: ArrayList<E>) {
252+
if (widget !is CategoryListWidget) {
253+
return
254+
}
255+
modList.scrollAmount = 0.0
256+
page = 0
257+
@Suppress("UNCHECKED_CAST")
258+
selectedCategories = entries as ArrayList<CategoryListEntry>
259+
query = ""
260+
if (selectedCategories.any { it.id == "updatable" } && selectedCategories.size > 1) {
261+
val last = selectedCategories.last()
262+
if (last.id == "updatable") {
263+
selectedCategories.removeIf { it.category.id != "updatable" }
264+
} else {
265+
selectedCategories.removeIf { it.category.id == "updatable" }
270266
}
271-
modList.scrollAmount = 0.0
272-
page = 0
273-
selectedCategory = entry as CategoryListEntry
274-
query = ""
275-
showModsByCategory()
267+
categoryList.setSelected(selectedCategories)
268+
return
276269
}
270+
showModsByCategory()
277271
}
278272

279273
override fun tick() {

src/main/kotlin/xyz/deathsgun/modmanager/gui/widget/CategoryListEntry.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import net.minecraft.client.util.math.MatrixStack
2121
import net.minecraft.text.OrderedText
2222
import net.minecraft.text.Text
2323
import net.minecraft.util.Language
24-
import xyz.deathsgun.modmanager.api.gui.list.ListWidget
24+
import xyz.deathsgun.modmanager.api.gui.list.MultiSelectListWidget
2525
import xyz.deathsgun.modmanager.api.mod.Category
2626

2727

28-
class CategoryListEntry(list: ListWidget<CategoryListEntry>, val category: Category) :
29-
ListWidget.Entry<CategoryListEntry>(list, category.id) {
28+
class CategoryListEntry(list: MultiSelectListWidget<CategoryListEntry>, val category: Category) :
29+
MultiSelectListWidget.Entry<CategoryListEntry>(list, category.id) {
3030

3131
override fun render(
3232
matrices: MatrixStack?,

src/main/kotlin/xyz/deathsgun/modmanager/gui/widget/CategoryListWidget.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package xyz.deathsgun.modmanager.gui.widget
1818

1919
import net.minecraft.client.MinecraftClient
2020
import xyz.deathsgun.modmanager.api.gui.list.IListScreen
21-
import xyz.deathsgun.modmanager.api.gui.list.ListWidget
21+
import xyz.deathsgun.modmanager.api.gui.list.MultiSelectListWidget
2222
import xyz.deathsgun.modmanager.api.mod.Category
2323

2424
class CategoryListWidget(
@@ -29,7 +29,7 @@ class CategoryListWidget(
2929
bottom: Int,
3030
itemHeight: Int,
3131
parent: IListScreen
32-
) : ListWidget<CategoryListEntry>(client, width, height, top, bottom, itemHeight, parent) {
32+
) : MultiSelectListWidget<CategoryListEntry>(client, width, height, top, bottom, itemHeight, parent) {
3333

3434
override fun init() {
3535

0 commit comments

Comments
 (0)