Popup
Popup is an aggregate container whose renderer mounts in the global
popupHolder instead of its normal parent's renderer. This lets dropdowns and
other floating content draw above the ordinary view tree.
Lumen settings popup
Source: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/wow-samples/LumenTextToSpeechDetailsPage.tsprivate buildSettingsPopup(accentColor: string): ViewStruct<ViewState> {
return Popup([
Rectangle({ color: '#02070DEB', width: 1920, height: 1080 }),
Group([
Rectangle({
color: '#0B1C27',
width: 820,
height: 970,
}),
Rectangle({
color: accentColor,
width: 8,
height: 970,
}),
Label({
text: 'Playback & spoken guidance',
color: '#F3F5F7',
width: 700,
height: 54,
fontKey: '~theme.fonts.heading-40',
translation: [58, 42],
}),
Label({
text: 'Every control announces its role, value, position, and result.',
color: '#9FB0BC',
width: 700,
height: 36,
fontKey: '~theme.fonts.text-regular-20',
translation: [58, 105],
}),
Label({
text: 'SPOKEN BEHAVIOUR',
color: accentColor,
width: 680,
height: 28,
fontKey: '~theme.fonts.text-bold-14',
translation: [58, 164],
}),
CheckBox({
id: 'lumenSpeechAutoNarrateCheck',
text: 'Narrate new screens automatically',
isChecked: this.autoNarrate,
labelStyleKey: 'controls.Label.checkbox',
width: 680,
height: 52,
translation: [58, 208],
})
.audioGuideHint('Press OK to toggle automatic screen introductions.')
.nextFocusMap({ up: 'lumenSpeechDoneButton', down: 'lumenSpeechQuietCheck' })
.onCheckChange((event) => this.setAutoNarrate(event)),
CheckBox({
id: 'lumenSpeechQuietCheck',
text: 'Lower background audio while speaking',
isChecked: this.quietBackground,
labelStyleKey: 'controls.Label.checkbox',
width: 680,
height: 52,
translation: [58, 276],
})
.audioGuideHint('Press OK to toggle background audio silence during narration.')
.nextFocusMap({ up: 'lumenSpeechAutoNarrateCheck', down: 'lumenSpeechMilestonesCheck' })
.onCheckChange((event) => this.setQuietBackground(event)),
CheckBox({
id: 'lumenSpeechMilestonesCheck',
text: 'Announce playback milestones',
isChecked: this.announceMilestones,
labelStyleKey: 'controls.Label.checkbox',
width: 680,
height: 52,
translation: [58, 344],
})
.audioGuideHint('Press OK to toggle spoken playback progress announcements.')
.nextFocusMap({ up: 'lumenSpeechQuietCheck', down: 'lumenSpeechDetailCombo' })
.onCheckChange((event) => this.setAnnounceMilestones(event)),
Label({
text: 'DESCRIPTION DETAIL',
color: accentColor,
width: 680,
height: 28,
fontKey: '~theme.fonts.text-bold-14',
translation: [58, 426],
}),
ComboBox({
id: 'lumenSpeechDetailCombo',
items: DETAIL_ITEMS,
selectedIndex: this.detailIndex,
width: 680,
height: 64,
styleKey: 'controls.ComboBox.default',
maxVisibleItems: 3,
translation: [58, 460],
})
.audioGuideHint('Description detail. Press OK to choose concise, balanced, or rich narration.')
.nextFocusMap({ up: 'lumenSpeechMilestonesCheck', down: 'lumenSpeechQueueCombo' })
.onSelectionChange((event) => this.selectDetail(event)),
Label({
text: 'WHEN NEW GUIDANCE ARRIVES',
color: accentColor,
width: 680,
height: 28,
fontKey: '~theme.fonts.text-bold-14',
translation: [58, 550],
}),
ComboBox({
id: 'lumenSpeechQueueCombo',
items: QUEUE_ITEMS,
selectedIndex: this.queueIndex,
width: 680,
height: 64,
styleKey: 'controls.ComboBox.default',
maxVisibleItems: 2,
translation: [58, 584],
})
.audioGuideHint('Speech queue behaviour. Press OK to choose whether new guidance interrupts or waits.')
.nextFocusMap({ up: 'lumenSpeechDetailCombo', down: 'lumenSpeechPreviewSettingsButton' })
.onSelectionChange((event) => this.selectQueueMode(event)),
Rectangle({
color: '#FFFFFF24',
width: 680,
height: 1,
translation: [58, 684],
}),
Label({
text: 'Preview speaks the current story using these exact choices.',
color: '#9FB0BC',
width: 680,
height: 34,
fontKey: '~theme.fonts.text-medium-18',
translation: [58, 716],
}),
Button({
id: 'lumenSpeechPreviewSettingsButton',
text: 'PLAY Preview voice',
width: 326,
height: 64,
styleKey: 'controls.Button.lumenAiSearchCta',
translation: [58, 782],
})
.audioGuideItem('Preview voice. Press OK to hear the current story with these playback and spoken guidance settings.')
.nextFocusMap({ up: 'lumenSpeechQueueCombo', right: 'lumenSpeechDoneButton', down: 'lumenSpeechAutoNarrateCheck' })
.onClick(() => this.playSpokenPreview()),
Button({
id: 'lumenSpeechDoneButton',
text: 'Done',
width: 326,
height: 64,
styleKey: 'controls.Button.lumenAiSearchCta',
translation: [412, 782],
})
.audioGuideItem('Done. Press OK to save these settings and return to the story details screen.')
.nextFocusMap({ up: 'lumenSpeechQueueCombo', left: 'lumenSpeechPreviewSettingsButton', down: 'lumenSpeechAutoNarrateCheck' })
.onClick(() => this.closeSettings()),
Label({
text: 'BACK closes this popup / OK changes the focused control',
color: '#FFFFFF70',
width: 680,
height: 28,
fontKey: '~theme.fonts.text-bold-14',
translation: [58, 898],
}),
])
.id('lumenSpeechSettingsPanel')
.width(820)
.height(970)
.translation([1020, 54]),
])
.id('lumenSpeechSettingsPopup')
.width(1920)
.height(1080);
}Popup([
Image({
imageUri: 'pkg:/assets/images/popup@res.9.png',
width: 480,
height: 240,
}),
VGroup(menuButtons)
.translation([24, 20])
.itemSpacing(12),
])
.visible(this.isMenuOpen);
The popup renderer is anchored to the owning view's bounds. Place its children relative to that anchor, and drive visibility from owning state.
Popup reports a measured width and height of zero so it does not occupy space
in the parent's layout. It is only a floating render container; it does not
provide dialog focus locking, dismissal, or a backdrop. Use Hosanna's dialog
manager for modal UI.