@@ -51,13 +51,13 @@ import org.polyfrost.polyui.utils.fastEach
5151import org.polyfrost.polyui.utils.image
5252import org.polyfrost.polyui.utils.levenshteinDistance
5353import org.polyfrost.polyui.utils.mapToArray
54+ import java.lang.ref.WeakReference
5455import kotlin.math.PI
5556
5657open class ConfigVisualizer {
5758 private val LOGGER = LogManager .getLogger(" OneConfig/Config" )
58- protected val cache = HashMap <Tree , Map < String , Map < String , ArrayList < Triple < String , String ?, Drawable >>>> >()
59+ protected val configs = HashMap <Tree , Drawable >()
5960 protected val optBg = rgba(39 , 49 , 55 , 0.2f )
60- protected val alignC = Align (cross = Align .Cross .Start )
6161 protected val alignCV = Align (cross = Align .Cross .Start , mode = Align .Mode .Vertical )
6262 protected val alignVNoPad = Align (cross = Align .Cross .Start , mode = Align .Mode .Vertical , pad = Vec2 .ZERO )
6363 protected val stdAlign = Align (main = Align .Main .SpaceBetween , pad = Vec2 (16f , 8f ))
@@ -69,24 +69,37 @@ open class ConfigVisualizer {
6969 /* *
7070 * For information, see [create].
7171 */
72- fun get (config : Tree ) = create(config)
72+ fun get (config : Tree ) = configs.getOrPut(config) { create(config) }
7373
7474 fun getMatching (str : String ): List <Drawable > {
75- if (str.length < 2 ) return emptyList()
75+ val it = str.trim()
76+ if (it.length < 2 ) return emptyList()
7677 val out = ArrayList <Drawable >()
77- for ((tree, opts) in cache) {
78- for ((category, sub) in opts) {
79- for ((header, options) in sub) {
80- for ((title, desc, drawable) in options) {
81- if (title.contains(str, ignoreCase = true ) || title.levenshteinDistance(str) <= 2 ) out .add(drawable)
82- else if (desc != null && (desc.contains(str, ignoreCase = true ) || desc.levenshteinDistance(str) <= 2 )) out .add(drawable)
83- }
78+ for (config in configs.keys) {
79+ getMatching(it, config, out )
80+ }
81+ return out
82+ }
83+
84+ private fun getMatching (it : String , config : Tree , out : ArrayList <Drawable >) {
85+ config.onAll { _, node ->
86+ if (node is Tree ) {
87+ getMatching(it, node, out )
88+ return @onAll
89+ }
90+ val visualized = node.getMetadata<WeakReference <Drawable >>(" drawable" )?.get() ? : return @onAll
91+ if (node.title.matchesSearch(it) || node.description.matchesSearch(it)) {
92+ out .add(visualized)
93+ } else {
94+ node.getMetadata<ArrayList <String >>(" aliases" )?.fastEach { alias ->
95+ if (alias.matchesSearch(it)) out .add(visualized)
8496 }
8597 }
8698 }
87- return out
8899 }
89100
101+ private fun String?.matchesSearch (search : String ) = this != null && (this .contains(search, ignoreCase = true ) || this .levenshteinDistance(search) <= 2 )
102+
90103 /* *
91104 * Turn the given config tree into a PolyUI representation.
92105 *
@@ -105,20 +118,17 @@ open class ConfigVisualizer {
105118 config : Tree ,
106119 initialPage : String = "General ",
107120 ): Drawable {
108- val options = cache.getOrPut(config) {
109- val now = System .nanoTime()
110- val options = HashMap <String , HashMap <String , ArrayList <Triple <String , String ?, Drawable >>>>(4 )
121+ val now = System .nanoTime()
122+ val options = HashMap <String , HashMap <String , ArrayList <Drawable >>>(4 )
111123
112- // asm: step 1: sort the tree into a map of:
113- // categories
114- // -> subcategories
115- // -> list of options
116- for ((_, node) in config.map) {
117- processNode(node, options)
118- }
119- LOGGER .info(" creating config page ${config.title} took ${(System .nanoTime() - now) / 1_000_000f } ms" )
120- options
124+ // asm: step 1: sort the tree into a map of:
125+ // categories
126+ // -> subcategories
127+ // -> list of options
128+ for ((_, node) in config.map) {
129+ processNode(node, options)
121130 }
131+ LOGGER .info(" creating config page ${config.title} took ${(System .nanoTime() - now) / 1_000_000f } ms" )
122132 return makeFinal(flattenSubcategories(options), initialPage)
123133 }
124134
@@ -131,13 +141,13 @@ open class ConfigVisualizer {
131141 )
132142 }
133143
134- protected open fun flattenSubcategories (options : Map <String , Map <String , ArrayList <Triple < String , String ?, Drawable > >>>): Map <String , Drawable > {
144+ protected open fun flattenSubcategories (options : Map <String , Map <String , ArrayList <Drawable >>>): Map <String , Drawable > {
135145 return options.mapValues { (_, subcategories) ->
136146 Group (
137147 children = subcategories.mapToArray { (header, opts) ->
138148 Group (
139149 Text (header, fontSize = 22f ),
140- * opts.mapToArray { it.third } ,
150+ * opts.toTypedArray() ,
141151 alignment = alignCV,
142152 )
143153 },
@@ -146,7 +156,7 @@ open class ConfigVisualizer {
146156 }
147157 }
148158
149- protected open /* suspend? */ fun processNode (node : Node , options : HashMap <String , HashMap <String , ArrayList <Triple < String , String ?, Drawable > >>>) {
159+ protected open /* suspend? */ fun processNode (node : Node , options : HashMap <String , HashMap <String , ArrayList <Drawable >>>) {
150160 val icon =
151161 when (val it = node.getMetadata<Any ?>(" icon" )) {
152162 null -> null
@@ -162,14 +172,14 @@ open class ConfigVisualizer {
162172 val list = options.getOrPut(category) { HashMap (4 ) }.getOrPut(subcategory) { ArrayList (8 ) }
163173 if (node is Property <* >) {
164174 val vis = node.getVisualizer() ? : return
165- list.add(Triple (node.title, node.description, wrap(vis.visualize(node), node.title ? : return , node.description, icon).addHideHandler(node)))
175+ list.add(wrap(vis.visualize(node), node.title ? : return , node.description, icon).addHideHandler(node).linkTo(node ))
166176 } else {
167177 node as Tree
168178 if (node.map.isEmpty()) {
169179 LOGGER .warn(" sub-tree ${node.id} is empty; ignoring" )
170180 return
171181 }
172- list.add(Triple (node.title, node.description, makeAccordion(node, node.title ? : return , node.description, icon)))
182+ list.add(makeAccordion(node, node.title ? : return , node.description, icon).linkTo(node ))
173183 }
174184 }
175185
@@ -190,13 +200,11 @@ open class ConfigVisualizer {
190200 desc : String? ,
191201 icon : PolyImage ? ,
192202 ): Drawable {
193- val index = ArrayList <Pair <String , String ?>>(tree.map.size)
194203 val options =
195204 tree.map.mapNotNull map@{ (_, node) ->
196205 if (node !is Property <* >) return @map null
197206 val vis = node.getVisualizer() ? : return @map null
198- index.add(node.title to node.description)
199- wrapForAccordion(vis.visualize(node), node.title ? : return @map null , node.description).addHideHandler(node)
207+ wrapForAccordion(vis.visualize(node), node.title ? : return @map null , node.description).addHideHandler(node).linkTo(node)
200208 }
201209
202210 var open = false
@@ -241,6 +249,7 @@ open class ConfigVisualizer {
241249 },
242250 Image (" polyui/chevron-down.svg" ).also { it.rotation = PI }
243251 )
252+ @Suppress(" UNCHECKED_CAST" ) // reason: #already-type-checked
244253 enabled = e as Property <Boolean >
245254 } else {
246255 toWrap = Image (" polyui/chevron-down.svg" ).also { it.rotation = PI }
@@ -249,7 +258,7 @@ open class ConfigVisualizer {
249258 wrap(toWrap, title, desc, icon).also {
250259 it.color = PolyColor .TRANSPARENT
251260 it.onClick {
252- if (enabled != null && ! enabled.getAs<Boolean >()) return @onClick
261+ if (enabled != null && ! enabled.getAs<Boolean >()) return @onClick
253262 this .openInsn(null )
254263 }
255264 },
@@ -261,7 +270,6 @@ open class ConfigVisualizer {
261270 color = optBg,
262271 alignment = alignVNoPad,
263272 ).namedId(" AccordionHeader" )
264- // index.fastEach { this.index[out] = it }
265273 return out
266274 }
267275
@@ -342,6 +350,12 @@ open class ConfigVisualizer {
342350 return this
343351 }
344352
353+ private fun Drawable.linkTo (node : Node ): Drawable {
354+ // asm: stored in a weak reference to avoid potential memory leaks
355+ node.addMetadata(" drawable" , WeakReference (this ))
356+ return this
357+ }
358+
345359 companion object {
346360 @JvmField
347361 val INSTANCE = ConfigVisualizer ()
0 commit comments