Pane Manager
The Pane Manager
is the Slot-based layout that builds up the entire HMI’s “scaffold”. The PaneManager
serves as the root node in the HMI Window
The PaneManager
Composable has the following API, which reveals the parts of the HMI:
@Composable
fun PaneManager(
//Children should not use max size.
banner: @Composable (() -> Unit)?,
//Children can use fillMaxSize, and it just works.
sideSplit: @Composable (() -> Unit)?,
darkenBackgroundOnSideSplitDisplay : Boolean = false,
sideSplitVisible: Boolean,
//Children should not use max size.
bottomPanel: @Composable (() -> Unit)?,
//This is for notifications
//Children should use max size
topPopIn: @Composable (() -> Unit)?,
topPopInVisible: Boolean,
//Children should use max size.
mainContent: @Composable () -> Unit,
mainContentOverlay : (@Composable () -> Unit)? = null
) {
...
}
The PaneManagerDebugger
window is a great way to see the PaneManager
in action.
Here we see the slots for the notification and side bar. The reason for the wild gradient is so that I could tell whether the main content area was being scaled or truncated as the banner
and bottomBar
areas were enabled/disabled.
This screenshot shows the Notification body filling in the notification slot, as well as the Model Menu
overlay with some dummy data. The Chip orientation
for the modal menu is set to NW
(top left).
Providing “screen parts” for the PaneManager
Given that the PaneManager
can take in @Composable () -> Unit
lambdas, how does this allow the PaneManager to actually show content in the HMI Window
?
The trick is that each lamda is fed in from a Flow<@Composable () -> Unit>
in the MenuWindow
.
This is also where all the global CompositionLocal
s are set up. Composition Local docs
@Composable
private fun rootContent() {
//TODO, a KnobListener needs to be a CompositionLocal passed down all the way through
//TODO so we can avoid chains of passing it in as a screen parameter.
//TODO this is the root node of the composition so it's a pretty good place to put it.
//TODO https://developer.android.com/jetpack/compose/compositionlocal
val dummyKnobListenerService = KnobListenerService(MutableSharedFlow())
val providedKnobListenerService = remember { mutableStateOf(realKnobListenerService) }
ThemeWrapper.ThemedUiWrapper(
themeConfigurationStorage.getTheme().collectAsState(themeConfigurationStorage.getStoredTheme()).value
) {
PaneManager(
banner = null,
sideSplit = {
modalMenuService.sidePaneOverlay.collectAsState().value.let {
if (it.ui != null) {
providedKnobListenerService.value = dummyKnobListenerService
CompositionLocalProvider(
MenuWindowKnobListener provides realKnobListenerService
) {
it.ui?.invoke()
}
} else {
providedKnobListenerService.value = realKnobListenerService
}
}
},
darkenBackgroundOnSideSplitDisplay = modalMenuService.sidePaneOverlay.collectAsState().value.darkenBackground,
sideSplitVisible = modalMenuService.sidePaneOverlay.collectAsState().value.ui != null,
bottomPanel = {
val scope = rememberCoroutineScope()
scope.launch {
bottomBarClock.updateValues()
}
BmwFullScreenBottomBar(
date = bottomBarClock.dateFlow.collectAsState().value,
time = bottomBarClock.timeFlow.collectAsState().value,
)
},
topPopIn = {
notificationHub.currentNotification.collectAsState().value?.toView()
},
topPopInVisible = notificationHub.currentNotificationIsVisible.collectAsState(false).value,
mainContent = {
Box(
Modifier.fillMaxSize()
) {
val currentNode = navigator.mainContentScreen.collectAsState()
logger.d("MenuWindow", currentNode.value.node.thisClass.canonicalName)
logger.d("MenuWindow", currentNode.value.incomingResult.toString())
CompositionLocalProvider(
MenuWindowKnobListener provides providedKnobListenerService.value
) {
with(currentNode.value) {
node.provideMainContent().invoke(incomingResult)
}
}
}
},
mainContentOverlay = {
modalMenuService.modalMenuOverlay.collectAsState().value.let {
if (it != null) {
providedKnobListenerService.value = dummyKnobListenerService
it.invoke()
} else {
providedKnobListenerService.value = realKnobListenerService
}
}
}
)
}
}
There exists a dummyKnobListenerService
and a realKnobListenerService
, which
are switched out so that the knob turn events don’t update the main menu and the modal
overlay menu at the same time.