Add search bar to top of visual libraries

This commit is contained in:
1hitsong 2025-02-22 09:55:35 -05:00
parent 470f3279c3
commit ae31423b52
11 changed files with 219 additions and 110 deletions

View file

@ -297,6 +297,7 @@ sub loadItems()
item.name = tr("View All") + " " + item.name
row.json = item
row.type = "Folder"
row.itemType = m.top.itemType
if LCase(m.top.itemType) = "movie"
genreItemImage = api.items.GetImageURL(item.id)

View file

@ -35,6 +35,12 @@ sub setupNodes()
m.dropdownOptions = m.top.findNode("dropdownOptions")
m.submitButton = m.top.findNode("submitButton")
m.searchButton = m.top.findNode("searchButton")
m.searchButton.textColor = ColorPalette.DARKGREY
m.searchButton.focusTextColor = ColorPalette.WHITE
m.searchButton.background = ColorPalette.WHITE
m.searchButton.focusBackground = ColorPalette.HIGHLIGHT
m.sortButton = m.top.findNode("sortButton")
m.sortButton.textColor = ColorPalette.DARKGREY
m.sortButton.focusTextColor = ColorPalette.WHITE
@ -63,6 +69,7 @@ end sub
sub init()
m.top.optionsAvailable = false
setupNodes()
m.bypassSearchEvent = false
m.itemGrid.focusBitmapBlendColor = ColorPalette.HIGHLIGHT
m.genreList.focusBitmapBlendColor = ColorPalette.HIGHLIGHT
@ -204,7 +211,7 @@ sub loadInitialItems()
m.filterOptions = ParseJson(m.filterOptions)
m.loadItemsTask.searchTerm = m.voiceBox.text
m.loadItemsTask.searchTerm = m.top.searchTerm <> string.EMPTY ? m.top.searchTerm : m.voiceBox.text
m.loadItemsTask.sortField = m.sortField
m.loadItemsTask.sortAscending = m.sortAscending
m.loadItemsTask.filter = m.filter
@ -380,10 +387,13 @@ function setMovieOptions() as object
options.views = [
{ "Title": tr("Presentation"), "Name": "presentation", "Track": { "description": tr("Presentation") } },
{ "Title": tr("Grid"), "Name": "grid", "Track": { "description": tr("Grid") } },
{ "Title": tr("Genres"), "Name": "Genres", "Track": { "description": tr("Genres") } }
{ "Title": tr("Grid"), "Name": "grid", "Track": { "description": tr("Grid") } }
]
if not isStringEqual(m.top.mediaType, ItemType.MYLIST)
options.views.push({ "Title": tr("Genres"), "Name": "Genres", "Track": { "description": tr("Genres") } })
end if
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName", "Track": { "description": tr("TITLE") } },
{ "Title": tr("Community Rating"), "Name": "CommunityRating,SortName", "Track": { "description": tr("Community Rating") } },
@ -1052,6 +1062,70 @@ sub onGenreItemSelected()
m.top.selectedItem = m.genreList.content.getChild(m.genreList.rowItemSelected[0]).getChild(m.genreList.rowItemSelected[1])
end sub
sub processDropdownState(searchTerm as string)
if not isStringEqual(searchTerm, string.EMPTY)
resetDropdownsToDefaultState()
setDropdownDisabledState(true)
return
end if
setDropdownDisabledState(false)
end sub
sub setDropdownDisabledState(disabledState = false as boolean)
m.sortButton.disabled = disabledState
m.sortOrderButton.disabled = disabledState
m.filterButton.disabled = disabledState
end sub
sub resetDropdownsToDefaultState()
m.filter = "All"
m.filterOptions = {}
if isStringEqual(getCollectionType(), ItemType.BOXSET)
m.sortField = "PremiereDate,SortName"
else if isStringEqual(getCollectionType(), ItemType.MYLIST)
m.sortField = "OrderAdded"
else
m.sortField = "SortName"
end if
m.sortAscending = true
' Reset view to defaults
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", "true")
set_user_setting("display." + m.top.parentItem.Id + ".filter", m.filter)
set_user_setting("display." + m.top.parentItem.Id + ".filterOptions", FormatJson(m.filterOptions))
if not isStringEqual(m.view, "Genres")
set_user_setting(`display.${m.top.mediaType}library.defaultview`, m.view)
end if
m.getFiltersTask.control = "RUN"
end sub
sub onSearchTermChanged()
m.searchButton.text = m.top.searchTerm
m.searchButton.focus = false
processDropdownState(m.top.searchTerm)
if m.bypassSearchEvent
m.bypassSearchEvent = false
return
end if
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.genreData = CreateObject("roSGNode", "ContentNode")
m.genreList.content = m.genreData
loadInitialItems()
end sub
sub alphaSelectedChanged()
' Allow user to toggle by clicking letter twice
if isStringEqual(m.loadItemsTask.nameStartsWith, m.top.alphaSelected)
@ -1160,43 +1234,46 @@ end sub
sub onSortChange()
m.global.sceneManager.unobserveFieldScoped("returnData")
sortChoice = m.global.sceneManager.returnData
m.sortButton.focus = false
if not isChainValid(sortChoice, "name") then return
if isStringEqual(sortChoice.LookupCI("name"), m.sortField) then return
m.sortButton.focus = false
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
if not isStringEqual(sortChoice.LookupCI("name"), m.sortField)
m.sortField = sortChoice.LookupCI("name")
m.sortButton.text = sortChoice.LookupCI("title")
m.sortField = sortChoice.LookupCI("name")
m.sortButton.text = sortChoice.LookupCI("title")
m.sortAscending = true
m.sortAscending = true
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
end if
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
end sub
sub onViewChange()
m.global.sceneManager.unobserveFieldScoped("returnData")
viewChoice = m.global.sceneManager.returnData
m.viewButton.focus = false
if not isChainValid(viewChoice, "name") then return
if isStringEqual(viewChoice.LookupCI("name"), m.view) then return
m.viewButton.focus = false
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
if isStringEqual(viewChoice.LookupCI("name"), m.view) then return
m.view = viewChoice.LookupCI("name")
m.viewButton.text = viewChoice.LookupCI("title")
set_user_setting("display." + m.top.parentItem.Id + ".landing", m.view)
' Reset any filtering or search terms
m.bypassSearchEvent = true
m.top.searchTerm = string.EMPTY
m.top.alphaSelected = string.EMPTY
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = string.EMPTY
@ -1233,46 +1310,47 @@ end sub
sub onSortOrderChange()
m.global.sceneManager.unobserveFieldScoped("returnData")
sortOrderChoice = m.global.sceneManager.returnData
m.sortOrderButton.focus = false
if not isChainValid(sortOrderChoice, "name") then return
if m.sortAscending = isStringEqual(sortOrderChoice.LookupCI("name"), "ascending") then return
m.sortOrderButton.focus = false
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
if not isStringEqual(sortOrderChoice.LookupCI("name"), m.sortAscending.toStr())
m.sortAscending = isStringEqual(sortOrderChoice.LookupCI("name"), "Ascending")
m.sortOrderButton.text = sortOrderChoice.LookupCI("title")
m.sortAscending = isStringEqual(sortOrderChoice.LookupCI("name"), "Ascending")
m.sortOrderButton.text = sortOrderChoice.LookupCI("title")
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", m.sortAscending.toStr())
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", m.sortAscending.toStr())
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
end if
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
end sub
' Using the user's cursor position, determine which dropdown is above them
function getOverhangingButton()
if m.itemGrid.isinFocusChain()
if m.itemGrid.numColumns = 4
buttonList = [m.sortButton, m.sortOrderButton, m.filterButton, m.viewButton]
buttonList = [m.searchButton, m.sortButton, m.filterButton, m.viewButton]
return buttonList[m.itemGrid.currFocusColumn]
end if
if m.itemGrid.numColumns = 7
if m.itemGrid.currFocusColumn <= 1 then return m.sortButton
if m.itemGrid.currFocusColumn <= 2 then return m.sortOrderButton
if m.itemGrid.currFocusColumn <= 4 then return m.filterButton
if m.itemGrid.currFocusColumn = 0 then return m.searchButton
if m.itemGrid.currFocusColumn <= 2 then return m.sortButton
if m.itemGrid.currFocusColumn <= 3 then return m.sortOrderButton
if m.itemGrid.currFocusColumn <= 5 then return m.filterButton
return m.viewButton
end if
end if
if m.genreList.isinFocusChain()
if m.genreList.currFocusColumn <= 1 then return m.sortButton
if m.genreList.currFocusColumn <= 2 then return m.sortOrderButton
if m.genreList.currFocusColumn <= 4 then return m.filterButton
if m.genreList.currFocusColumn <= 0 then return m.searchButton
if m.genreList.currFocusColumn <= 2 then return m.sortButton
if m.genreList.currFocusColumn <= 3 then return m.sortOrderButton
if m.genreList.currFocusColumn <= 5 then return m.filterButton
return m.viewButton
end if
@ -1295,7 +1373,18 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if isStringEqual(key, KeyCode.OK)
if m.searchButton.isinFocusChain()
buttonData = [
tr("Search")
]
m.global.sceneManager.callFunc("keyboardDialog", "searchLibrary", tr("Search this library"), [""], buttonData, m.top.searchTerm, m.top.parentItem.Id)
return true
end if
if m.sortButton.isinFocusChain()
if m.sortButton.disabled then return false
sortData = {
data: m.options.options.sort
}
@ -1306,6 +1395,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if m.sortOrderButton.isinFocusChain()
if m.sortOrderButton.disabled then return false
sortOrderData = {
data: [
{ Title: tr("Ascending"), Name: "ascending", Track: { description: tr("Ascending") } },
@ -1325,6 +1416,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if m.filterButton.isinFocusChain()
if m.filterButton.disabled then return false
m.options.visible = true
m.options.findNode("filterMenu").setFocus(true)
group = m.global.sceneManager.callFunc("getActiveScene")
@ -1345,7 +1438,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
if m.dropdownOptions.isinFocusChain()
if isStringEqual(key, KeyCode.LEFT)
if m.sortButton.isinFocusChain()
if m.searchButton.isinFocusChain()
m.searchButton.focus = false
m.sortButton.focus = false
m.sortOrderButton.focus = false
m.viewButton.focus = false
@ -1358,6 +1452,15 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
end if
if m.sortButton.isinFocusChain()
m.searchButton.focus = true
m.searchButton.setFocus(true)
m.sortButton.focus = false
group = m.global.sceneManager.callFunc("getActiveScene")
group.lastFocus = m.searchButton
return true
end if
if m.sortOrderButton.isinFocusChain()
m.sortButton.focus = true
m.sortButton.setFocus(true)
@ -1387,6 +1490,15 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if isStringEqual(key, KeyCode.Right)
if m.searchButton.isinFocusChain()
m.searchButton.focus = false
m.sortButton.focus = true
m.sortButton.setFocus(true)
group = m.global.sceneManager.callFunc("getActiveScene")
group.lastFocus = m.sortButton
return true
end if
if m.sortButton.isinFocusChain()
m.sortButton.focus = false
m.sortOrderButton.focus = true
@ -1418,6 +1530,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
if isStringEqual(key, KeyCode.DOWN)
if m.loadedItems = 0 then return false
m.searchButton.focus = false
m.sortButton.focus = false
m.sortOrderButton.focus = false
m.viewButton.focus = false
@ -1497,7 +1610,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
group = m.global.sceneManager.callFunc("getActiveScene")
group.lastFocus = m.sortButton
else
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
group = m.global.sceneManager.callFunc("getActiveScene")

View file

@ -9,58 +9,70 @@
<poster id="backdropTransition" loadDisplayMode="scaleToFill" width="1100" height="700" opacity="1" />
</maskGroup>
<LayoutGroup id="dropdownOptions" layoutDirection="horiz" translation="[100, 60]" itemSpacings="[30]">
<LayoutGroup id="dropdownOptions" layoutDirection="horiz" translation="[100, 60]" itemSpacings="[15]">
<LayoutGroup layoutDirection="vert" itemSpacings="[10]">
<Label font="font:SmallSystemFont" text="Search" />
<TextButton
id="searchButton"
fontSize="28"
padding="35"
icon="pkg:/images/icons/search-dark.png"
focusIcon="pkg:/images/icons/search-light.png"
text=""
height="75"
width="335" />
</LayoutGroup>
<LayoutGroup layoutDirection="vert" itemSpacings="[10]">
<Label id="sortLabel" font="font:SmallSystemFont" text="Sort By" />
<TextButton
id="sortButton"
iconSide="right"
fontSize="30"
fontSize="28"
padding="35"
icon="pkg:/images/icons/dropdown-dark.png"
focusIcon="pkg:/images/icons/dropdown-light.png"
text=""
height="75"
width="410" />
width="335" />
</LayoutGroup>
<LayoutGroup layoutDirection="vert" itemSpacings="[10]">
<Label id="sortOrderLabel" font="font:SmallSystemFont" text="Sort Order" />
<TextButton
id="sortOrderButton"
iconSide="right"
fontSize="30"
fontSize="28"
padding="35"
icon="pkg:/images/icons/dropdown-dark.png"
focusIcon="pkg:/images/icons/dropdown-light.png"
text=""
height="75"
width="410" />
width="335" />
</LayoutGroup>
<LayoutGroup layoutDirection="vert" itemSpacings="[10]">
<Label id="filterLabel" font="font:SmallSystemFont" text="Filter" />
<TextButton
id="filterButton"
iconSide="right"
fontSize="30"
fontSize="28"
padding="35"
icon="pkg:/images/icons/dropdown-dark.png"
focusIcon="pkg:/images/icons/dropdown-light.png"
text=""
height="75"
width="410" />
width="335" />
</LayoutGroup>
<LayoutGroup layoutDirection="vert" itemSpacings="[10]">
<Label id="viewLabel" font="font:SmallSystemFont" text="View" />
<TextButton
id="viewButton"
iconSide="right"
fontSize="30"
fontSize="28"
padding="35"
icon="pkg:/images/icons/dropdown-dark.png"
focusIcon="pkg:/images/icons/dropdown-light.png"
text=""
height="75"
width="410" />
width="335" />
</LayoutGroup>
</LayoutGroup>
@ -121,6 +133,7 @@
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
<field id="alphaSelected" type="string" alias="alpha.letterSelected" onChange="alphaSelectedChanged" alwaysNotify="true" />
<field id="searchTerm" type="string" onChange="onSearchTermChanged" alwaysNotify="true" />
<field id="alphaActive" type="boolean" value="false" />
<field id="showItemTitles" type="string" value="showonhover" />
</interface>

View file

@ -1,3 +1,5 @@
import "pkg:/source/enums/ColorPalette.bs"
sub init()
m.buttonBackground = m.top.findNode("buttonBackground")
m.buttonIcon = m.top.findNode("buttonIcon")
@ -13,6 +15,7 @@ sub init()
m.top.observeField("width", "onWidthChanged")
m.top.observeField("padding", "onPaddingChanged")
m.top.observeField("focus", "onFocusChanged")
m.top.observeField("disabled", "onDisabledChanged")
m.top.observeField("iconBlendColor", "onIconBlendColorChanged")
end sub
@ -33,6 +36,12 @@ sub onIconSideChanged()
end sub
sub onFocusChanged()
if m.top.disabled
m.buttonBackground.blendColor = m.top.focus ? ColorPalette.DARKGREY : ColorPalette.MIDGREY
m.buttonText.color = m.top.focus ? ColorPalette.MIDGREY : m.top.textColor
return
end if
if m.top.focus
m.buttonText.color = m.top.focusTextColor
m.buttonBackground.blendColor = m.top.focusBackground
@ -44,6 +53,11 @@ sub onFocusChanged()
end if
end sub
sub onDisabledChanged()
m.buttonBackground.blendColor = m.top.disabled ? ColorPalette.MIDGREY : m.top.background
onFocusChanged()
end sub
sub onBackgroundChanged()
m.top.unobserveField("background")
m.buttonBackground.blendColor = m.top.background

View file

@ -21,6 +21,7 @@
<field id="iconSide" type="string" value="left" />
<field id="selected" type="boolean" value="false" />
<field id="focus" type="boolean" />
<field id="disabled" type="boolean" />
<field id="escape" type="string" value="" />
</interface>
</component>

View file

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="FolderData" extends="JFContentItem">
<interface>
<field id="itemType" type="string" value="" />
</interface>
</component>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -605,17 +605,19 @@ sub onSelectedItemEvent(msg)
if isStringEqual(type(selectedItemType), "rostring") then selectedItemType = LCase(selectedItemType)
end if
if selectedItemType = "collectionfolder"
if isStringEqual(selectedItemType, "collectionfolder")
onLibrarySelection(selectedItem)
else if selectedItemType = "boxset"
else if isStringEqual(selectedItemType, "boxset")
group = CreateVisualLibraryScene(selectedItem, ItemType.BOXSET)
m.global.sceneManager.callFunc("pushScene", group)
else if selectedItemType = "folder" and isStringEqual(selectedItem.json.type, "genre")
else if isStringEqual(selectedItemType, "folder") and isStringEqual(selectedItem.json.type, "genre")
' User clicked on a genre folder
if selectedItem.json.MovieCount > 0
if isStringEqual(selectedItem.itemType, ItemType.MOVIE)
group = CreateVisualLibraryScene(selectedItem, ItemType.MOVIE)
else if selectedItem.json.SeriesCount > 0
else if isStringEqual(selectedItem.itemType, ItemType.SERIES)
group = CreateVisualLibraryScene(selectedItem, ItemType.SERIES)
else if isStringEqual(selectedItem.itemType, ItemType.MUSICVIDEO)
group = CreateVisualLibraryScene(selectedItem, ItemType.MUSICVIDEO)
else
group = CreateItemGrid(selectedItem)
end if
@ -1342,6 +1344,18 @@ sub onDataReturnedEvent(msg)
return
end if
if isStringEqual(selectedPopupID, "searchLibrary")
searchTerm = chainLookup(popupNode, "returndata.text")
if not isValid(searchTerm) then return
activeScene = m.global.sceneManager.callFunc("getActiveScene")
if not isValid(activeScene) then return
activeScene.searchTerm = searchTerm
return
end if
if isStringEqual(selectedPopupID, "libraryitem")
if not isValid(itemID) then return
processLibraryItemPopup(selectedPopupButton, itemID)
@ -1420,7 +1434,7 @@ sub processPlaylistPopup(selectedPopupButton as string, itemID as string)
]
stopLoadingSpinner()
m.global.sceneManager.callFunc("keyboardDialog", "newPlaylist", tr("Create New Playlist"), ["Input name of new playlist"], resumeData, tr("Name of new playlist"), itemID)
m.global.sceneManager.callFunc("keyboardDialog", "newPlaylist", tr("Create New Playlist"), ["Input name of new playlist"], resumeData, string.EMPTY, itemID)
return
end if
@ -1577,7 +1591,7 @@ sub processLibraryItemPopup(selectedPopupButton as string, itemID as string)
]
stopLoadingSpinner()
m.global.sceneManager.callFunc("keyboardDialog", "newPlaylist", tr("Create New Playlist"), ["Input name of new playlist"], resumeData, tr("Name of new playlist"), itemID)
m.global.sceneManager.callFunc("keyboardDialog", "newPlaylist", tr("Create New Playlist"), ["Input name of new playlist"], resumeData, string.EMPTY, itemID)
return
end if

View file

@ -1,54 +0,0 @@
[
{
"description": "Move Sort, Filter, and View options as dropdowns to top of the Music library",
"author": "1hitsong"
},
{
"description": "Add Play Album, Shuffle Play Album, and Instant Mix Album to option menu",
"author": "1hitsong"
},
{
"description": "Create add to playlist function on movie detail screen",
"author": "1hitsong"
},
{
"description": "Add playlist functions to movie library and audio library option menus",
"author": "1hitsong"
},
{
"description": "Add playlist functions to home option menu",
"author": "1hitsong"
},
{
"description": "Add my list functions to home option menu",
"author": "1hitsong"
},
{
"description": "Add ability to remove item from playlist",
"author": "1hitsong"
},
{
"description": "Add my list to my media row on home screen",
"author": "1hitsong"
},
{
"description": "Add ability to delete playlist",
"author": "1hitsong"
},
{
"description": "Fix overhang not showing when returning to album screen",
"author": "1hitsong"
},
{
"description": "Fix playback when transcoding long movies",
"author": "jimdogx"
},
{
"description": "Fix access to empty playlist",
"author": "1hitsong"
},
{
"description": "Apply color palette to album screen",
"author": "1hitsong"
}
]

View file

@ -0,0 +1,6 @@
[
{
"description": "Fix dropdowns getting out of sync if user backs out of popup menus",
"author": "1hitsong"
}
]