Repository: jacobsjo/mc-datapack-map
Pull request #84: Add toggle functionality for biome filters.
Author: login: agarleanu
Created: 2025-05-23T13:17:35Z | Merged: 2025-05-25T22:28:46Z

Closes #37.

https://github.com/user-attachments/assets/95e1d1ab-cc89-41ca-8621-356c19264aac



Linked issue #37: Toggleable Filter
There's no way to toggle a biome filter without the program clearing your filter entirely and having to manually re-type and select every biome again between toggles.

Especially when using datapacks like terralith, the sheer number of biomes can make the visuals very overwhelming and impossible to find specific biomes without the filter.  However, it is also necessary to see the contextual generation once you found a point of interest with the filter. As it is, doing so will wipe your filter and it would need to be manually set again each time, It can get very time consuming when scouting for a seed with desirable trailts.
Linked issue comments: user: login: agarleanu
created_at: 2025-05-23T11:09:51Z
body: I made a very simple implementation that does this. (I needed this feature too)

![Image](https://github.com/user-attachments/assets/fa85199e-5bba-4642-be36-f9f23c4e2c28)

@jacobsjo is this acceptable? Instead of skipping the loop when rendering I just darkened the color. Technically I'd do it with luminosity, but for the proof of concept I just subtracted from the (r,g,b) values. I'm thinking there is also some merit to not rendering filtered biomes at all, so I'll be making some changes to the menu to allow some level of configuration with regards to the filters.

@Hyperbuss does this look fine to you?
author_association: CONTRIBUTOR
user: login: jacobsjo
created_at: 2025-05-23T11:15:36Z
body: This isn't quite what the issue is proposing. The proposal here is to have a way to easily disable and then re-enable the biome selection. The problem with darkening is that we don't have any control over what the original biome colors are (since they come from the datapacks), so the original color could be black for example.
author_association: OWNER
user: login: agarleanu
created_at: 2025-05-23T11:29:49Z
body: Then it's an even simpler fix.

However, I think the original issue highlights a very common use case that is not currently covered, they even mention it by name - seeing the contextual generation.

The most complicated solution is a query system similar to what cubiomes does, but what casual users need is an overlay of sorts.

The color is not a problem, in my opinion. We can just make a color selector to change the overlay (instead of just darkening the pixels), or just go with a pattern overlay (as you have already done with the sea level).

In the interest of staying in-scope with this issue, I'll just submit a simple PR to solve this.

But I think everything else that I've mentioned is also worth considering.
author_association: CONTRIBUTOR
user: login: jacobsjo
created_at: 2025-05-23T12:09:24Z
body: If you find a way to have the highlighting work consistently without hiding everything else, I'd be happy to accept that as well. For example Terralith volcanic peaks has a completely black color, we'd want to be able to highlight that biome as well.
author_association: OWNER
user: login: agarleanu
created_at: 2025-05-23T13:21:43Z
body: For now I've made this PR (#84) just with this feature. I'd like to rebuild the menu, since adding colored overlays & other things on top of the existing one would just make everything look too busy.
author_association: CONTRIBUTOR

Diff:
diff --git a/locales/en.json b/locales/en.json
index 2691fc7..77ae32d 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -61,6 +61,7 @@
     "datapack_list.remove_datapack.title": "Remove pack",
     "menu.add.title": "Add Pack",
     "menu.search_biome.title": "Search Biome",
+    "menu.search_biome.toggle": "Toggle Biome Filter",
     "menu.structure_positions.title": "Structure Positions",
     "menu.reload_datapacks.title": "Reload Packs",
     "dropdown.add.zip": "Open Pack or Mod",
diff --git a/src/MapLayers/BiomeLayer.ts b/src/MapLayers/BiomeLayer.ts
index 5b60673..410af01 100644
--- a/src/MapLayers/BiomeLayer.ts
+++ b/src/MapLayers/BiomeLayer.ts
@@ -65,8 +65,10 @@ export class BiomeLayer extends L.GridLayer {
 			})
 
 
-		watch(this.searchStore.biomes, () => {
-			this.rerender()
+		watch([this.searchStore.biomes, () => this.searchStore.disabled], ([biomes, disabled], [oldBiomes, oldDisabled]) => {
+			// Do not re-render if no biomes were filtered, regardless of the disabled state
+			if (oldDisabled !== disabled && oldBiomes.size === 0 && biomes.size === 0) return
+			this.rerender() 
 		})
 
 		watch(do_hillshade, () => {
@@ -123,7 +125,10 @@ export class BiomeLayer extends L.GridLayer {
 			for (let z = 0; z < this.tileSize * this.calcResolution; z++) {
 				const biome = tile.array[x + 1][z + 1].biome
 
-				if (this.searchStore.biomes.size > 0 && !this.searchStore.biomes.has(biome)) {
+				if (this.searchStore.biomes.size > 0
+					&& !this.searchStore.biomes.has(biome)
+					&& !this.searchStore.disabled
+				) {
 					continue
 				}
 
diff --git a/src/components/MenuButtons.vue b/src/components/MenuButtons.vue
index 8aabc5c..fb7befa 100644
--- a/src/components/MenuButtons.vue
+++ b/src/components/MenuButtons.vue
@@ -22,18 +22,21 @@ function reload(event: MouseEvent){
     datapackStore.$patch({})
 }
 
-function clearBiomeSearch(event: Event){
+function toggleBiomeSearch() {
+    searchStore.disabled = !searchStore.disabled
+    searchStore.$patch({})
+}
+
+function clearBiomeSearch() {
     searchStore.biomes.clear()
     searchStore.$patch({})
     searchBiomeDropdownOpen.value = false
-    event.preventDefault()
 }
 
-function clearStructureSearch(event: Event){
+function clearStructureSearch() {
     searchStore.structures.clear()
     searchStore.$patch({})
     structureDropdownOpen.value = false
-    event.preventDefault()
 }
 
 
@@ -51,20 +54,30 @@ function clearStructureSearch(event: Event){
                 @click="openDropdownOpen = true"
                 @keypress.enter="openDropdownOpen = true"
             />
-            <font-awesome-icon
-                icon="fa-magnifying-glass"
-                class="button"
-                tabindex="0"
-                :class="{
-                    open: searchBiomeDropdownOpen,
-                    active: searchStore.biomes.size > 0
-                }"
-                :title="$t('menu.search_biome.title')"
-                @click="searchBiomeDropdownOpen = true"
-                @keypress.enter="searchBiomeDropdownOpen = true"
-                @contextmenu="clearBiomeSearch"    
-                @dblclick="clearBiomeSearch"
-            />
+            <span class="group-button" :class="{active: !searchStore.disabled}">
+                <font-awesome-icon
+                    icon="fa-magnifying-glass"
+                    class="button"
+                    tabindex="0"
+                    :class="{
+                        open: searchBiomeDropdownOpen,
+                        active: searchStore.biomes.size > 0
+                    }"
+                    :title="$t('menu.search_biome.title')"
+                    @click="searchBiomeDropdownOpen = true"
+                    @keypress.enter="searchBiomeDropdownOpen = true"
+                    @contextmenu.prevent="clearBiomeSearch"    
+                    @dblclick.prevent="clearBiomeSearch"
+                />
+                <font-awesome-icon
+                    v-if="searchStore.biomes.size > 0"
+                    :icon="!searchStore.disabled ? 'fa-toggle-on' : 'fa-toggle-off'"
+                    class="button transparent"
+                    tabindex="0"
+                    :title="$t('menu.search_biome.toggle')"
+                    @click.prevent="toggleBiomeSearch"
+                />
+            </span>
             <font-awesome-icon
                 icon="fa-location-dot"
                 class="button"
@@ -76,8 +89,8 @@ function clearStructureSearch(event: Event){
                 :title="$t('menu.structure_positions.title')"
                 @click="structureDropdownOpen = true"
                 @keypress.enter="structureDropdownOpen = true"
-                @contextmenu="clearStructureSearch"    
-                @dblclick="clearStructureSearch"
+                @contextmenu.prevent="clearStructureSearch"    
+                @dblclick.prevent="clearStructureSearch"
             />
 
             <font-awesome-icon v-if="settingsStore.dev_mode" icon="fa-rotate-right" class="button" tabindex="0" :title="$t('menu.reload_datapacks.title')" @click="reload" @keypress.enter="reload" />
@@ -138,7 +151,25 @@ function clearStructureSearch(event: Event){
 
 .button.active:hover {
     background-color: rgb(168, 192, 30);
+}
+
+.button.transparent {
+    background-color: transparent;
+}
+
+.button.transparent:hover {
+    background-color: transparent;
+}
+
+.group-button {
+    display: flex;
+    flex-direction: row;
+    border-radius: 0.5rem;
+    background-color: rgb(88, 88, 88);
+}
 
+.group-button.active {
+    background-color: rgb(77, 87, 23);
 }
 
 .spacer {
diff --git a/src/components/dropdown/FindBiomeDropdown.vue b/src/components/dropdown/FindBiomeDropdown.vue
index a8f626e..8b7ac6f 100644
--- a/src/components/dropdown/FindBiomeDropdown.vue
+++ b/src/components/dropdown/FindBiomeDropdown.vue
@@ -15,7 +15,7 @@ function toggleBiome(biome: Identifier){
     } else {
         searchStore.biomes.add(biome.toString())
     }
-    searchStore.$patch({}) // call $subscribe, not sure why this is necessary
+    searchStore.$patch({disabled: false})
 }
 
 function disableGroup(group: string){
@@ -23,6 +23,7 @@ function disableGroup(group: string){
         if (biome.startsWith(group + ":"))
             searchStore.biomes.delete(biome)
     });
+    searchStore.$patch({disabled: false})
 }
 
 function getColorString(biome: Identifier){
diff --git a/src/main.ts b/src/main.ts
index f56cd81..7fa9f21 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,7 +3,7 @@ import { createI18n } from 'vue-i18n'
 import { createPinia } from 'pinia'
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { faFileZipper, faFolderOpen, faXmark, faRotateRight, faPlus, faBars, faMagnifyingGlass, faLocationDot, faAngleRight, faAngleDown, faDice, faGlobe, faEarthEurope, faMountainSun, faWater, faArrowsDownToLine, faTableCells, faCircleQuestion } from "@fortawesome/free-solid-svg-icons";
+import { faFileZipper, faFolderOpen, faXmark, faRotateRight, faPlus, faBars, faMagnifyingGlass, faToggleOn, faToggleOff, faLocationDot, faAngleRight, faAngleDown, faDice, faGlobe, faEarthEurope, faMountainSun, faWater, faArrowsDownToLine, faTableCells, faCircleQuestion } from "@fortawesome/free-solid-svg-icons";
 import './style.css'
 import App from './App.vue'
 
@@ -13,7 +13,7 @@ import { registerSW } from 'virtual:pwa-register'
 
 registerSW({ immediate: true })
 
-library.add(faFileZipper, faFolderOpen, faXmark, faRotateRight, faPlus, faBars, faMagnifyingGlass, faLocationDot, faAngleRight, faAngleDown, faDice, faGlobe, faEarthEurope, faMountainSun, faWater, faArrowsDownToLine, faTableCells, faCircleQuestion);
+library.add(faFileZipper, faFolderOpen, faXmark, faRotateRight, faPlus, faBars, faMagnifyingGlass, faToggleOn, faToggleOff, faLocationDot, faAngleRight, faAngleDown, faDice, faGlobe, faEarthEurope, faMountainSun, faWater, faArrowsDownToLine, faTableCells, faCircleQuestion);
 
 
 const uri = window.location.search.substring(1)
diff --git a/src/stores/useBiomeSearchStore.ts b/src/stores/useBiomeSearchStore.ts
index b687340..2f27b9e 100644
--- a/src/stores/useBiomeSearchStore.ts
+++ b/src/stores/useBiomeSearchStore.ts
@@ -1,12 +1,14 @@
 import { Identifier, StructurePlacement, StructureSet, WorldgenStructure } from "deepslate";
 import { defineStore } from "pinia";
-import { computed, reactive } from "vue";
+import { computed, reactive, ref } from "vue";
 
 
 export const useSearchStore = defineStore('search', () => {
     const biomes = reactive<Set<string>>(new Set())
     const structures = reactive<Set<string>>(new Set())
 
+    const disabled = ref(false)
+
     const structure_sets = computed(() => {
         structures.size // make sure any change to structures triggers recomputation
         const sets: Identifier[] = []
@@ -39,5 +41,5 @@ export const useSearchStore = defineStore('search', () => {
         return {sets, has_invalid: has_invalid_set}
     })
 
-    return {biomes, structures, structure_sets}
+    return {biomes, structures, structure_sets, disabled}
 })


Reviews:
user: login: agarleanu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T13:22:40Z
user: login: jacobsjo
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T13:30:55Z
user: login: jacobsjo
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T13:32:41Z
user: login: jacobsjo
body: lgtm
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-25T22:25:29Z

Review comments:
diff_hunk: @@ -51,20 +54,38 @@ function clearStructureSearch(event: Event){
                 @click="openDropdownOpen = true"
                 @keypress.enter="openDropdownOpen = true"
             />
-            <font-awesome-icon
-                icon="fa-magnifying-glass"
-                class="button"
-                tabindex="0"
-                :class="{
-                    open: searchBiomeDropdownOpen,
-                    active: searchStore.biomes.size > 0
-                }"
-                :title="$t('menu.search_biome.title')"
-                @click="searchBiomeDropdownOpen = true"
-                @keypress.enter="searchBiomeDropdownOpen = true"
-                @contextmenu="clearBiomeSearch"    
-                @dblclick="clearBiomeSearch"
-            />
+            <span class="group-button" :class="{active: !searchStore.disabled}">
+                <font-awesome-icon
+                    icon="fa-magnifying-glass"
+                    class="button"
+                    tabindex="0"
+                    :class="{
+                        open: searchBiomeDropdownOpen,
+                        active: searchStore.biomes.size > 0
+                    }"
+                    :title="$t('menu.search_biome.title')"
+                    @click="searchBiomeDropdownOpen = true"
+                    @keypress.enter="searchBiomeDropdownOpen = true"
+                    @contextmenu.prevent="clearBiomeSearch"    
+                    @dblclick.prevent="clearBiomeSearch"
+                />
+                <font-awesome-icon
+                    v-show="!searchStore.disabled"
+                    icon="fa-toggle-on"
+                    class="button transparent"
+                    tabindex="0"
+                    :title="$t('menu.search_biome.title')"
path: src/components/MenuButtons.vue
user: login: agarleanu
body: Add new translation key with text, this is not "Search Biome", but rather "Toggle Biome Search".
created_at: 2025-05-23T13:22:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,20 +54,38 @@ function clearStructureSearch(event: Event){
                 @click="openDropdownOpen = true"
                 @keypress.enter="openDropdownOpen = true"
             />
-            <font-awesome-icon
-                icon="fa-magnifying-glass"
-                class="button"
-                tabindex="0"
-                :class="{
-                    open: searchBiomeDropdownOpen,
-                    active: searchStore.biomes.size > 0
-                }"
-                :title="$t('menu.search_biome.title')"
-                @click="searchBiomeDropdownOpen = true"
-                @keypress.enter="searchBiomeDropdownOpen = true"
-                @contextmenu="clearBiomeSearch"    
-                @dblclick="clearBiomeSearch"
-            />
+            <span class="group-button" :class="{active: !searchStore.disabled}">
+                <font-awesome-icon
+                    icon="fa-magnifying-glass"
+                    class="button"
+                    tabindex="0"
+                    :class="{
+                        open: searchBiomeDropdownOpen,
+                        active: searchStore.biomes.size > 0
+                    }"
+                    :title="$t('menu.search_biome.title')"
+                    @click="searchBiomeDropdownOpen = true"
+                    @keypress.enter="searchBiomeDropdownOpen = true"
+                    @contextmenu.prevent="clearBiomeSearch"    
+                    @dblclick.prevent="clearBiomeSearch"
+                />
+                <font-awesome-icon
+                    v-show="!searchStore.disabled"
+                    icon="fa-toggle-on"
+                    class="button transparent"
+                    tabindex="0"
+                    :title="$t('menu.search_biome.title')"
+                    @click.prevent="toggleBiomeSearch"
+                />
+                <font-awesome-icon
+                    v-show="searchStore.disabled"
+                    icon="fa-toggle-off"
+                    class="button transparent"
+                    tabindex="0"
+                    :title="$t('menu.search_biome.title')"
path: src/components/MenuButtons.vue
user: login: agarleanu
body: Same here.
created_at: 2025-05-23T13:22:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,20 +54,38 @@ function clearStructureSearch(event: Event){
                 @click="openDropdownOpen = true"
                 @keypress.enter="openDropdownOpen = true"
             />
-            <font-awesome-icon
-                icon="fa-magnifying-glass"
-                class="button"
-                tabindex="0"
-                :class="{
-                    open: searchBiomeDropdownOpen,
-                    active: searchStore.biomes.size > 0
-                }"
-                :title="$t('menu.search_biome.title')"
-                @click="searchBiomeDropdownOpen = true"
-                @keypress.enter="searchBiomeDropdownOpen = true"
-                @contextmenu="clearBiomeSearch"    
-                @dblclick="clearBiomeSearch"
-            />
+            <span class="group-button" :class="{active: !searchStore.disabled}">
+                <font-awesome-icon
+                    icon="fa-magnifying-glass"
+                    class="button"
+                    tabindex="0"
+                    :class="{
+                        open: searchBiomeDropdownOpen,
+                        active: searchStore.biomes.size > 0
+                    }"
+                    :title="$t('menu.search_biome.title')"
+                    @click="searchBiomeDropdownOpen = true"
+                    @keypress.enter="searchBiomeDropdownOpen = true"
+                    @contextmenu.prevent="clearBiomeSearch"    
+                    @dblclick.prevent="clearBiomeSearch"
+                />
+                <font-awesome-icon
+                    v-show="!searchStore.disabled"
+                    icon="fa-toggle-on"
+                    class="button transparent"
+                    tabindex="0"
+                    :title="$t('menu.search_biome.title')"
path: src/components/MenuButtons.vue
user: login: jacobsjo
body: You can add translation keys yourself, just add it to the `en_us.json` file, the rest will then be handled automatically by weblate.
created_at: 2025-05-23T13:30:55Z
author_association: OWNER
diff_hunk: @@ -51,20 +54,38 @@ function clearStructureSearch(event: Event){
                 @click="openDropdownOpen = true"
                 @keypress.enter="openDropdownOpen = true"
             />
-            <font-awesome-icon
-                icon="fa-magnifying-glass"
-                class="button"
-                tabindex="0"
-                :class="{
-                    open: searchBiomeDropdownOpen,
-                    active: searchStore.biomes.size > 0
-                }"
-                :title="$t('menu.search_biome.title')"
-                @click="searchBiomeDropdownOpen = true"
-                @keypress.enter="searchBiomeDropdownOpen = true"
-                @contextmenu="clearBiomeSearch"    
-                @dblclick="clearBiomeSearch"
-            />
+            <span class="group-button" :class="{active: !searchStore.disabled}">
+                <font-awesome-icon
+                    icon="fa-magnifying-glass"
+                    class="button"
+                    tabindex="0"
+                    :class="{
+                        open: searchBiomeDropdownOpen,
+                        active: searchStore.biomes.size > 0
+                    }"
+                    :title="$t('menu.search_biome.title')"
+                    @click="searchBiomeDropdownOpen = true"
+                    @keypress.enter="searchBiomeDropdownOpen = true"
+                    @contextmenu.prevent="clearBiomeSearch"    
+                    @dblclick.prevent="clearBiomeSearch"
+                />
+                <font-awesome-icon
+                    v-show="!searchStore.disabled"
+                    icon="fa-toggle-on"
path: src/components/MenuButtons.vue
user: login: jacobsjo
body: This shouldn't be 2 separate elements, rather just use `:icon="searchStore.disabled ? 'fa-toggle-off' : 'fa-toggle-on'`
created_at: 2025-05-23T13:32:41Z
author_association: OWNER

PR comments:
user: login: agarleanu
created_at: 2025-05-23T13:24:13Z
body: I'll have to return to this later today, if you have suggestions that are better than "Toggle Biome Search" for this translation key just let me know.
author_association: CONTRIBUTOR
user: login: jacobsjo
created_at: 2025-05-25T22:31:58Z
body: Thank you, this is a great addition. I made a few tiny changes (see the commit).

And again, if you want to make a PR that shows the non-selected biomes in some clear way, I'd be more than happy to accept that as well.
author_association: OWNER