Skip to content

Commit

Permalink
Queue UI (#199)
Browse files Browse the repository at this point in the history
* Refactor long press

* Lint fix

* Queue UI, refactor PlayQueue to use ContentNode

* Add queue notification hint

* Play at index

* Play queue with highlight

* Priority

* Comment

---------

Co-authored-by: github-action linter <[email protected]>
  • Loading branch information
iBicha and github-action linter authored Nov 12, 2023
1 parent e070945 commit 3559a3f
Show file tree
Hide file tree
Showing 24 changed files with 523 additions and 64 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- User interface for the Queue: Press and hold `Options (*)` to show the queue

## [0.15.1] - 2023-11-10

### Added
Expand Down
20 changes: 17 additions & 3 deletions playlet-lib/src/components/AppController/AppController.bs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import "pkg:/components/Dialog/DialogUtils.bs"
import "pkg:/components/Navigation/LongPress.bs"
import "pkg:/components/PlayQueue/PlayQueueViewUtils.bs"
import "pkg:/components/VideoPlayer/VideoUtils.bs"
import "pkg:/source/utils/FocusManagement.bs"
import "pkg:/source/utils/Logging.bs"
Expand All @@ -10,6 +12,7 @@ function Init()
end function

function OnNodeReady()
InitializeLongPress(["options"])
NodeSetFocus(m.top.root, true)
end function

Expand Down Expand Up @@ -78,15 +81,26 @@ function GetRootScreen(screenName as string) as object
end function

function onKeyEvent(key as string, press as boolean) as boolean
if not press
return false
if LongPressHandler(key, press)
return true
end if

if key = OPTIONS_LONG_PRESS_KEY and press
LogInfo("Opening PlayQueueView")
PlayQueueViewUtils.Open(m.top.playQueue, m.top)
return true
end if
if key = "options"

if key = "options" and not press
if VideoUtils.ToggleVideoPictureInPicture()
return true
end if
end if

if not press
return false
end if

if key = "play"
if VideoUtils.TogglePause()
return true
Expand Down
1 change: 1 addition & 0 deletions playlet-lib/src/components/AppController/AppController.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<interface>
<field id="root" type="node" />
<field id="stack" type="node" />
<field id="playQueue" type="node" />
<function name="PushScreen" />
<function name="PopScreen" />
<function name="FocusTopScreen" />
Expand Down
5 changes: 4 additions & 1 deletion playlet-lib/src/components/MainScene.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<children>
<!-- Logger should remain the first child, to make sure it is initialized first in the scene -->
<Logger id="Logger" />
<AppController id="AppController" root="bind:./AppRoot" stack="bind:./Stack">
<AppController id="AppController"
root="bind:./AppRoot"
stack="bind:./Stack"
playQueue="bind:./PlayQueue">
<Group id="Stack">
<AppRoot id="AppRoot">
<NavBar id="NavBar" focusIndex="1" appController="bind:/AppController">
Expand Down
3 changes: 2 additions & 1 deletion playlet-lib/src/components/MainScene_bindings.transpiled.brs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ function InitializeBindings()
childProps: {
"AppController": {
"root": "./AppRoot",
"stack": "./Stack"
"stack": "./Stack",
"playQueue": "./PlayQueue"
},
"NavBar": {
"appController": "/AppController"
Expand Down
70 changes: 56 additions & 14 deletions playlet-lib/src/components/Navigation/LongPress.bs
Original file line number Diff line number Diff line change
@@ -1,49 +1,91 @@
const LONG_PRESS_KEY = "OK_LONG_PRESS"
const OK_LONG_PRESS_KEY = "OK_LONG_PRESS"
const OPTIONS_LONG_PRESS_KEY = "OPTIONS_LONG_PRESS"

function InitializeLongPress(keys as object, duration = 1.0 as float)
longPressKeyMap = {
"OK": OK_LONG_PRESS_KEY,
"options": OPTIONS_LONG_PRESS_KEY
}
m._longPressKeyMap = {}
for each key in keys
m._longPressKeyMap[key] = longPressKeyMap[key]
end for

#if DEBUG
' This is for testing long press using the VSCode extension.
' Using the keyboard:
' - press K to simulate an "OK" long press on the remote.
' - press O to simulate an "options" long press on the remote.
simulatedLongPressKeyMap = {
"OK": "Lit_k",
"options": "Lit_o"
}

m._simulatedLongPressKeyMap = {}
for each key in keys
m._simulatedLongPressKeyMap[simulatedLongPressKeyMap[key]] = key
end for
#end if

function InitializeLongPressTimer(duration = 1.0 as float)
m._longPressTimer = CreateObject("roSGNode", "Timer")
m._longPressTimer.duration = duration
m._longPressFired = false
m._pressState = {}

m._longPressTimer.ObserveField("fire", FuncName(LongPressTimerFired))
end function

function LongPressHandler(key as string, press as boolean) as boolean
#if DEBUG
if key = "Lit_l"
LogInfo("Simulating long press using key:", key, "press:", press)
if m._simulatedLongPressKeyMap.DoesExist(key)
subtype = m.top.subtype()
simulatedKey = m._simulatedLongPressKeyMap[key]
LogInfo(`[${subtype}] Simulating`, simulatedKey, "long press using key:", key, "press:", press)
longPressKey = m._longPressKeyMap[simulatedKey]
m._longPressFired = true
return OnkeyEvent(LONG_PRESS_KEY, press)
return OnkeyEvent(longPressKey, press)
end if
#end if
if key = "OK" and press

isLongPressKey = m._longPressKeyMap.DoesExist(key)
if not isLongPressKey
return false
end if

if press
m._longPressTimer.control = "stop"
m._longPressTimer.control = "start"
m._longPressFired = false
m._okPressed = true
m._longPressKey = key
m._pressState[key] = true
return true
end if

if key = "OK" and not press
if not press
m._longPressTimer.control = "stop"
if m._longPressFired = true
m._longPressFired = false
m._okPressed = false
m._pressState[key] = false
return true
end if
' This case is for handling a dialog: The dialog will be dismissed with the OK down event,
' and we would trigger the action with the OK up event. The action should only happen if
' we detected both down and up events.
wasOkPressed = m._okPressed
m._okPressed = false
if wasOkPressed <> true
wasPressed = m._pressState[key]
m._pressState[key] = false
if wasPressed <> true
return true
end if
end if

return false
end function

function LongPressTimerFired()
function LongPressTimerFired() as void
m._longPressFired = true
OnkeyEvent(LONG_PRESS_KEY, true)
if StringUtils.IsNullOrEmpty(m._longPressKey) or not m._longPressKeyMap.DoesExist(m._longPressKey)
return
end if

OnkeyEvent(m._longPressKeyMap[m._longPressKey], true)
end function
5 changes: 5 additions & 0 deletions playlet-lib/src/components/NodeByRef/ContentNodeReference.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<component name="ContentNodeReference" extends="ContentNode">
<interface>
<field id="refNode" type="node" />
</interface>
</component>
41 changes: 41 additions & 0 deletions playlet-lib/src/components/NodeByRef/NodeByRef.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
' NodeByRef: these functions provides the ability to "wrap" a node in
' another node, so it can be passed by reference. This is useful for
' when a node is required to be a child of multiple nodes at once,
' which is not possible.
' Instead, the node is wrapped, passed around, and unwrapped when needed.
import "pkg:/source/utils/StringUtils.bs"

namespace NodeByRef

function Wrap(node as object) as object
if node = invalid
return invalid
end if

if node.refNode <> invalid
return node
end if

ref = CreateObject("roSGNode", "ContentNodeReference")
ref.refNode = node
if not StringUtils.IsNullOrEmpty(node.id)
ref.id = node.id + "-ref"
end if
return ref
end function

function Unwrap(node as object) as object
if node = invalid
return invalid
end if

loopGuard = 0
while node.refNode <> invalid and loopGuard < 10
node = node.refNode
loopGuard += 1
end while

return node
end function

end namespace
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "pkg:/components/NodeByRef/NodeByRef.bs"
import "pkg:/source/utils/StringUtils.bs"
import "pkg:/source/utils/Types.bs"

Expand All @@ -14,6 +15,7 @@ end function

function OnContentSet() as void
content = m.top.content
content = NodeByRef.Unwrap(content)
if content = invalid
return
end if
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<children>
<Poster
width="500"
height="126"
height="144"
opacity="0.9"
uri="pkg:/images/white.9.png">

Expand Down Expand Up @@ -40,6 +40,12 @@
color="0x262626ff"
wrap="true" />
</LayoutGroup>
<Label
width="480"
font="font:SmallestSystemFont"
color="0x262626ff"
text="Press and hold Options (*) to open the queue"
translation="[10,120]" />
</Poster>
<Animation
id="translationAnimation"
Expand Down
Loading

0 comments on commit 3559a3f

Please sign in to comment.