use frus_core::{Rect, Scene, SizeClass};
use frus_layout::Style;
use crate::interaction::{Key, Status};
use crate::portal::Placement;
use crate::runtime::Edit;
use crate::scroll::Axis;
use crate::theme::Theme;
use crate::widget::Widget;
pub struct Responsive<Msg = crate::callback::Callback> {
class_rank: u8,
inner: Option<Box<dyn Widget<Msg>>>,
inner_rank: u8,
}
fn closeness(rank: u8, class: u8) -> (u8, bool) {
(rank.abs_diff(class), rank > class)
}
impl<Msg> Responsive<Msg> {
pub fn new(class: SizeClass) -> Self {
Self {
class_rank: class.rank(),
inner: None,
inner_rank: 0,
}
}
fn consider(mut self, rank: u8, widget: impl Widget<Msg> + 'static) -> Self {
let better = self.inner.is_none()
|| closeness(rank, self.class_rank) < closeness(self.inner_rank, self.class_rank);
if better {
self.inner = Some(Box::new(widget));
self.inner_rank = rank;
}
self
}
pub fn compact(self, widget: impl Widget<Msg> + 'static) -> Self {
self.consider(SizeClass::Compact.rank(), widget)
}
pub fn medium(self, widget: impl Widget<Msg> + 'static) -> Self {
self.consider(SizeClass::Medium.rank(), widget)
}
pub fn expanded(self, widget: impl Widget<Msg> + 'static) -> Self {
self.consider(SizeClass::Expanded.rank(), widget)
}
}
pub fn responsive<Msg>(width: f32) -> Responsive<Msg> {
Responsive::new(SizeClass::from_width(width))
}
impl<Msg> Widget<Msg> for Responsive<Msg> {
fn style(&self) -> Style {
self.inner.as_ref().map(|w| w.style()).unwrap_or_default()
}
fn style_themed(&self, theme: &Theme) -> Style {
self.inner
.as_ref()
.map(|w| w.style_themed(theme))
.unwrap_or_default()
}
fn build_themed(&self, theme: &Theme) {
if let Some(w) = &self.inner {
w.build_themed(theme);
}
}
fn build_in(
&self,
id: crate::interaction::WidgetId,
runtime: &crate::runtime::Runtime,
theme: &Theme,
) {
if let Some(w) = &self.inner {
w.build_in(id, runtime, theme);
}
}
fn expand(
&self,
id: crate::interaction::WidgetId,
runtime: &crate::runtime::Runtime,
theme: &Theme,
) {
if let Some(w) = &self.inner {
w.expand(id, runtime, theme);
}
}
fn switches(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.switches())
}
fn system_ui_style(&self) -> Option<crate::SystemUiOverlayStyle> {
self.inner.as_ref().and_then(|w| w.system_ui_style())
}
fn debug_name(&self) -> &'static str {
self.inner
.as_ref()
.map(|w| w.debug_name())
.unwrap_or("Responsive")
}
fn semantics(&self) -> Option<frus_core::SemanticsProperties> {
self.inner.as_ref().and_then(|w| w.semantics())
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
self.inner.as_ref().map(|w| w.children()).unwrap_or(&[])
}
fn paint(&self, bounds: Rect, status: Status, theme: &Theme, scene: &mut Scene) {
if let Some(w) = &self.inner {
w.paint(bounds, status, theme, scene);
}
}
fn paints_over(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.paints_over())
}
fn paint_over(&self, bounds: Rect, status: Status, theme: &Theme, scene: &mut Scene) {
if let Some(w) = &self.inner {
w.paint_over(bounds, status, theme, scene);
}
}
fn on_click(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_click())
}
fn on_focus_change(&self, focused: bool) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_focus_change(focused))
}
fn on_tap_outside(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_tap_outside())
}
fn opaque(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.opaque())
}
fn positional_click(&self, local_x: f32, local_y: f32, width: f32, height: f32) -> Option<Msg> {
self.inner
.as_ref()
.and_then(|w| w.positional_click(local_x, local_y, width, height))
}
fn cursor_icon(
&self,
local_x: f32,
local_y: f32,
width: f32,
height: f32,
) -> Option<crate::interaction::Cursor> {
self.inner
.as_ref()
.and_then(|w| w.cursor_icon(local_x, local_y, width, height))
}
fn key(&self) -> Option<u64> {
self.inner.as_ref().and_then(|w| w.key())
}
fn on_edit(&self, edit: &mut Edit, key: &Key) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_edit(edit, key))
}
fn cursor_at(
&self,
local_x: f32,
local_y: f32,
width: f32,
scroll_cursor: usize,
) -> Option<usize> {
self.inner
.as_ref()
.and_then(|w| w.cursor_at(local_x, local_y, width, scroll_cursor))
}
fn text_metrics(&self, width: f32, cursor: usize) -> Option<(f32, f32, f32, f32)> {
self.inner
.as_ref()
.and_then(|w| w.text_metrics(width, cursor))
}
fn text_viewport(&self, rect: frus_core::Rect) -> Option<frus_core::Rect> {
self.inner.as_ref().and_then(|w| w.text_viewport(rect))
}
fn caret_vertical(
&self,
width: f32,
cursor: usize,
down: bool,
page: bool,
goal_x: Option<f32>,
) -> Option<(usize, f32)> {
self.inner
.as_ref()
.and_then(|w| w.caret_vertical(width, cursor, down, page, goal_x))
}
fn selected_text(&self, edit: &Edit) -> Option<String> {
self.inner.as_ref().and_then(|w| w.selected_text(edit))
}
fn text_value(&self) -> Option<&str> {
self.inner.as_ref().and_then(|w| w.text_value())
}
fn takes_typing(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.takes_typing())
}
fn selection_text(&self) -> Option<&str> {
self.inner.as_ref().and_then(|w| w.selection_text())
}
fn region_toolbar(
&self,
context: crate::ToolbarContext,
width: f32,
range: (usize, usize),
) -> Option<(Rect, &dyn Widget<Msg>)> {
self.inner
.as_ref()
.and_then(|w| w.region_toolbar(context, width, range))
}
fn selection_grip(
&self,
width: f32,
index: usize,
start: bool,
) -> Option<crate::SelectionHandle> {
self.inner
.as_ref()
.and_then(|w| w.selection_grip(width, index, start))
}
fn selection_hit(&self, local_x: f32, local_y: f32, width: f32) -> Option<usize> {
self.inner
.as_ref()
.and_then(|w| w.selection_hit(local_x, local_y, width))
}
fn word_at(&self, index: usize) -> Option<(usize, usize)> {
self.inner.as_ref().and_then(|w| w.word_at(index))
}
fn selection_handles(
&self,
width: f32,
edit: &Edit,
scroll_y: f32,
) -> Option<[crate::SelectionHandle; 2]> {
self.inner
.as_ref()
.and_then(|w| w.selection_handles(width, edit, scroll_y))
}
fn selection_toolbar(&self, context: crate::ToolbarContext) -> Option<&dyn Widget<Msg>> {
self.inner
.as_ref()
.and_then(|w| w.selection_toolbar(context))
}
fn selection_anchor(&self, width: f32, edit: &Edit, scroll_y: f32) -> Option<Rect> {
self.inner
.as_ref()
.and_then(|w| w.selection_anchor(width, edit, scroll_y))
}
fn edit_action(&self) -> Option<crate::EditAction> {
self.inner.as_ref().and_then(|w| w.edit_action())
}
fn autofill_hints(&self) -> &[crate::AutofillHint] {
self.inner
.as_ref()
.map(|w| w.autofill_hints())
.unwrap_or(&[])
}
fn autofill_group(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.autofill_group())
}
fn area_toolbar(&self, context: crate::ToolbarContext) -> Option<Option<&dyn Widget<Msg>>> {
self.inner.as_ref().and_then(|w| w.area_toolbar(context))
}
fn focusable(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.focusable())
}
fn draws_own_focus(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.draws_own_focus())
}
fn stack(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.stack())
}
fn stack_fit(&self) -> crate::StackFit {
self.inner
.as_ref()
.map_or(crate::StackFit::Passthrough, |w| w.stack_fit())
}
fn stack_measured(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.stack_measured())
}
fn stack_clips(&self) -> bool {
self.inner.as_ref().is_none_or(|w| w.stack_clips())
}
fn stack_direction(&self) -> Option<frus_core::TextDirection> {
self.inner.as_ref().and_then(|w| w.stack_direction())
}
fn stack_visible(&self) -> Option<usize> {
self.inner.as_ref().and_then(|w| w.stack_visible())
}
fn continuous(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.continuous())
}
fn virtual_list(&self, viewport: frus_core::Size) -> Option<crate::list::VirtualList<'_, Msg>> {
self.inner.as_ref().and_then(|w| w.virtual_list(viewport))
}
fn page_view(&self) -> Option<crate::pageview::PagedView<'_, Msg>> {
self.inner.as_ref().and_then(|w| w.page_view())
}
fn intrinsic(&self) -> Option<(crate::constraints::IntrinsicAxis, Option<f32>)> {
self.inner.as_ref().and_then(|w| w.intrinsic())
}
fn overflow_box(&self) -> Option<crate::constraints::Overflow> {
self.inner.as_ref().and_then(|w| w.overflow_box())
}
fn hero_tag(&self) -> Option<u64> {
self.inner.as_ref().and_then(|w| w.hero_tag())
}
fn drag_payload(&self) -> Option<u64> {
self.inner.as_ref().and_then(|w| w.drag_payload())
}
fn drag_needs_long_press(&self) -> bool {
self.inner
.as_ref()
.is_some_and(|w| w.drag_needs_long_press())
}
fn drag_ghost_opacity(&self) -> f32 {
self.inner.as_ref().map_or(1.0, |w| w.drag_ghost_opacity())
}
fn on_dropped(&self, accepted: bool) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_dropped(accepted))
}
fn drop_zone(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.drop_zone())
}
fn accepts_drag(&self, payload: u64) -> bool {
self.inner.as_ref().is_none_or(|w| w.accepts_drag(payload))
}
fn on_drop(&self, payload: u64) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_drop(payload))
}
fn on_page_changed(&self, page: usize) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_page_changed(page))
}
fn on_scroll(&self, position: crate::ScrollPosition) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_scroll(position))
}
fn scroll_grain(&self) -> f32 {
self.inner.as_ref().map_or(0.0, |w| w.scroll_grain())
}
fn layout_builder(&self) -> Option<&dyn Fn(frus_core::Size) -> Box<dyn Widget<Msg>>> {
self.inner.as_ref().and_then(|w| w.layout_builder())
}
fn scroll_overlay(&self) -> Option<&crate::scrolloverlay::OverlayBuilder<Msg>> {
self.inner.as_ref().and_then(|w| w.scroll_overlay())
}
fn draggable(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.draggable())
}
fn drag_track(&self) -> crate::widget::DragTrack {
self.inner
.as_ref()
.map_or(crate::widget::DragTrack::WHOLE, |w| w.drag_track())
}
fn on_thumb_drag(
&self,
thumb: usize,
moment: crate::widget::ThumbDrag,
fraction: f32,
) -> Option<Msg> {
self.inner
.as_ref()
.and_then(|w| w.on_thumb_drag(thumb, moment, fraction))
}
fn on_drag(&self, fraction: f32) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_drag(fraction))
}
fn on_drag_start(&self, fraction: f32) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_drag_start(fraction))
}
fn on_drag_end(&self, fraction: f32) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_drag_end(fraction))
}
fn on_drag_delta(&self, dx: f32) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_drag_delta(dx))
}
fn reorder_index(&self) -> Option<usize> {
self.inner.as_ref().and_then(|w| w.reorder_index())
}
fn on_reorder(&self, to: usize) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_reorder(to))
}
fn announce(&self) -> Option<String> {
self.inner.as_ref().and_then(|w| w.announce())
}
fn overlay_traps_focus(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.overlay_traps_focus())
}
fn scroll_content(&self) -> Option<&dyn Widget<Msg>> {
self.inner.as_ref().and_then(|w| w.scroll_content())
}
fn scroll_axis(&self) -> Axis {
self.inner
.as_ref()
.map(|w| w.scroll_axis())
.unwrap_or(Axis::Vertical)
}
fn scroll_physics(&self) -> Option<crate::physics::ScrollPhysics> {
self.inner.as_ref().and_then(|w| w.scroll_physics())
}
fn keep_visible(
&self,
size: frus_core::Size,
theme: &crate::theme::Theme,
) -> Option<crate::ui::KeepVisible> {
self.inner
.as_ref()
.and_then(|w| w.keep_visible(size, theme))
}
fn ime(&self) -> crate::ime::Ime {
self.inner.as_ref().map(|w| w.ime()).unwrap_or_default()
}
fn overlay(&self) -> Option<(&dyn Widget<Msg>, Placement)> {
self.inner.as_ref().and_then(|w| w.overlay())
}
fn overlay_dismiss(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.overlay_dismiss())
}
fn overlay_scrim(&self, theme: &Theme) -> Option<frus_core::Color> {
self.inner.as_ref().and_then(|w| w.overlay_scrim(theme))
}
fn anim_target(&self) -> Option<f32> {
self.inner.as_ref().and_then(|w| w.anim_target())
}
fn anim_duration(&self) -> f32 {
self.inner
.as_ref()
.map(|w| w.anim_duration())
.unwrap_or(crate::runtime::ANIM_DURATION)
}
fn anim_curve(&self) -> frus_core::Curve {
self.inner
.as_ref()
.map(|w| w.anim_curve())
.unwrap_or(frus_core::Curve::Linear)
}
fn opacity_group(&self) -> Option<f32> {
self.inner.as_ref().and_then(|w| w.opacity_group())
}
fn anim_color(&self) -> Option<frus_core::Color> {
self.inner.as_ref().and_then(|w| w.anim_color())
}
fn anim_size(&self) -> Option<frus_core::Size> {
self.inner.as_ref().and_then(|w| w.anim_size())
}
fn anim_radius(&self) -> Option<frus_core::BorderRadius> {
self.inner.as_ref().and_then(|w| w.anim_radius())
}
fn anim_padding(&self) -> Option<frus_core::Insets> {
self.inner.as_ref().and_then(|w| w.anim_padding())
}
fn anim_transform(&self) -> Option<crate::runtime::TransformValues> {
self.inner.as_ref().and_then(|w| w.anim_transform())
}
fn anim_offset(&self) -> Option<(f32, f32)> {
self.inner.as_ref().and_then(|w| w.anim_offset())
}
fn anim_pins(&self) -> Option<crate::positioned::Positioning> {
self.inner.as_ref().and_then(|w| w.anim_pins())
}
fn anim_fractions(&self) -> Option<(Option<f32>, Option<f32>)> {
self.inner.as_ref().and_then(|w| w.anim_fractions())
}
fn anim_text_style(&self) -> Option<frus_core::TextStyle> {
self.inner.as_ref().and_then(|w| w.anim_text_style())
}
fn positioned(&self) -> Option<crate::positioned::Positioning> {
self.inner.as_ref().and_then(|w| w.positioned())
}
fn alignment_geometry(&self) -> Option<frus_core::AlignmentGeometry> {
self.inner.as_ref().and_then(|w| w.alignment_geometry())
}
fn transform_translate(&self) -> Option<(f32, f32)> {
self.inner.as_ref().and_then(|w| w.transform_translate())
}
fn transform_scale(&self) -> Option<(f32, f32, frus_core::Alignment)> {
self.inner.as_ref().and_then(|w| w.transform_scale())
}
fn transform_rotate(&self) -> Option<(f32, frus_core::Alignment)> {
self.inner.as_ref().and_then(|w| w.transform_rotate())
}
fn clip_shape(&self) -> Option<frus_core::ClipShape> {
self.inner.as_ref().and_then(|w| w.clip_shape())
}
fn clip_path(&self) -> Option<&frus_core::Path> {
self.inner.as_ref().and_then(|w| w.clip_path())
}
fn barrier(&self) -> Option<crate::barrier::ModalBarrier> {
self.inner.as_ref().and_then(|w| w.barrier())
}
fn dismissible(&self) -> Option<crate::dismiss::DismissSpec> {
self.inner.as_ref().and_then(|w| w.dismissible())
}
fn on_dismissed(&self, direction: crate::dismiss::DismissDirection) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_dismissed(direction))
}
fn sheet(&self) -> Option<&crate::sheet::SheetSpec> {
self.inner.as_ref().and_then(|w| w.sheet())
}
fn on_sheet_dismissed(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_sheet_dismissed())
}
fn refresh(&self) -> Option<crate::refresh::RefreshSpec> {
self.inner.as_ref().and_then(|w| w.refresh())
}
fn on_refresh(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_refresh())
}
fn interactive(&self) -> Option<(f32, f32)> {
self.inner.as_ref().and_then(|w| w.interactive())
}
fn fitted(&self) -> Option<frus_core::BoxFit> {
self.inner.as_ref().and_then(|w| w.fitted())
}
fn rotated_quarter_turns(&self) -> Option<i32> {
self.inner.as_ref().and_then(|w| w.rotated_quarter_turns())
}
fn navigator(&self) -> Option<(f32, bool)> {
self.inner.as_ref().and_then(|w| w.navigator())
}
fn navigator_gesture(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.navigator_gesture())
}
fn navigator_retained(&self) -> usize {
self.inner.as_ref().map_or(0, |w| w.navigator_retained())
}
fn measure(&self, theme: &Theme) -> Option<frus_layout::MeasureFn<'_>> {
self.inner.as_ref().and_then(|w| w.measure(theme))
}
fn measure_key(&self, theme: &Theme) -> Option<u64> {
self.inner.as_ref().and_then(|w| w.measure_key(theme))
}
fn on_long_press(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_long_press())
}
fn on_double_tap(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_double_tap())
}
fn on_secondary_tap(&self) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_secondary_tap())
}
fn pan_axis(&self) -> Option<crate::PanAxis> {
self.inner.as_ref().and_then(|w| w.pan_axis())
}
fn hover_region(&self) -> Option<crate::HoverRegion> {
self.inner.as_ref().and_then(|w| w.hover_region())
}
fn pointer_listener(&self) -> bool {
self.inner.as_ref().is_some_and(|w| w.pointer_listener())
}
fn on_pointer_event(&self, event: crate::ListenerEvent) -> Vec<Msg> {
self.inner
.as_ref()
.map(|w| w.on_pointer_event(event))
.unwrap_or_default()
}
fn on_hover_event(&self, event: crate::HoverEvent) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_hover_event(event))
}
fn on_pan(&self, event: crate::PanEvent) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_pan(event))
}
fn pan_value(&self, event: crate::PanEvent, value: f32, rtl: bool) -> Option<f32> {
self.inner
.as_ref()
.and_then(|w| w.pan_value(event, value, rtl))
}
fn on_value_release(&self, value: f32) -> Option<Msg> {
self.inner.as_ref().and_then(|w| w.on_value_release(value))
}
fn on_key(&self, key: &crate::Key) -> crate::KeyResponse<Msg> {
self.inner
.as_ref()
.map(|w| w.on_key(key))
.unwrap_or(crate::KeyResponse::Ignored)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Container;
use frus_layout::Dimension;
fn chosen_width(r: &Responsive<()>) -> Dimension {
Widget::<()>::style(r).width
}
fn tagged(w: f32) -> Responsive<()> {
responsive(w)
.compact(Container::new().width(100.0))
.medium(Container::new().width(200.0))
.expanded(Container::new().width(300.0))
}
#[test]
fn wrappers_forward_painting_over_the_children() {
let slider = || crate::RangeSlider::<()>::new(0.2, 0.8);
let chosen = responsive::<()>(400.0).compact(slider());
assert!(Widget::<()>::paints_over(&chosen));
assert!(!Widget::<()>::paints_over(
&responsive::<()>(400.0).compact(Container::new())
));
let keyed = crate::Keyed::new(7, slider());
assert!(Widget::<()>::paints_over(&keyed));
let mut over = frus_core::Scene::new();
Widget::<()>::paint_over(
&keyed,
frus_core::Rect::new(0.0, 0.0, 248.0, 48.0),
Status::default(),
&crate::Theme::default(),
&mut over,
);
let mut chose = frus_core::Scene::new();
Widget::<()>::paint_over(
&chosen,
frus_core::Rect::new(0.0, 0.0, 248.0, 48.0),
Status::default(),
&crate::Theme::default(),
&mut chose,
);
assert!(!over.primitives().is_empty(), "the thumbs, through the key");
assert_eq!(over.primitives().len(), chose.primitives().len());
}
#[test]
fn picks_variant_for_width() {
assert_eq!(chosen_width(&tagged(400.0)), Dimension::Length(100.0));
assert_eq!(chosen_width(&tagged(700.0)), Dimension::Length(200.0));
assert_eq!(chosen_width(&tagged(1200.0)), Dimension::Length(300.0));
}
#[test]
fn falls_back_to_nearest_when_missing() {
let only_expanded = |w: f32| responsive::<()>(w).expanded(Container::new().width(300.0));
assert_eq!(
chosen_width(&only_expanded(300.0)),
Dimension::Length(300.0)
);
let two = responsive::<()>(700.0)
.compact(Container::new().width(100.0))
.expanded(Container::new().width(300.0));
assert_eq!(chosen_width(&two), Dimension::Length(100.0));
}
#[test]
fn a_selector_keeps_the_layers_place_in_the_stack() {
let pinned = responsive::<()>(400.0).compact(
crate::Positioned::new(Container::new().width(20.0).height(20.0))
.top(8.0)
.right(12.0),
);
let spec = Widget::<()>::positioned(&pinned).expect("the pins reached through");
assert_eq!((spec.top, spec.right), (Some(8.0), Some(12.0)));
}
#[test]
fn a_selector_keeps_the_childs_animated_offset() {
let sliding = responsive::<()>(400.0).compact(crate::AnimatedAlign::new(
frus_core::Alignment::CENTER_RIGHT,
0.2,
frus_core::Curve::Linear,
Container::new().width(20.0).height(20.0),
));
assert_eq!(
Widget::<()>::anim_offset(&sliding),
Some((1.0, 0.0)),
"the anchor's two fractions reached through"
);
}
#[test]
fn a_selector_builds_a_deferred_variant() {
let chosen = responsive::<()>(400.0).compact(crate::ThemeBuilder::new(|_: &Theme| {
Container::new()
.width(30.0)
.height(10.0)
.color(frus_core::Color::rgb(1.0, 0.0, 0.0))
}));
let ui = crate::ui::build_ui(
&chosen,
frus_core::Size::new(100.0, 100.0),
&crate::Runtime::default(),
&Theme::default(),
);
assert!(
ui.scene()
.primitives()
.iter()
.any(|p| matches!(p, frus_core::Primitive::Rect { color, .. } if color.r > 0.5)),
"the variant was built, and painted"
);
}
}