Components / layout
Responsive builder
A container that builds its content from its own measured width.
A builder is RenderOnce. Construct and mount it in one expression.
use gpui_kit::layout::Responsive;
Reviewed in responsive.
Also drawn by adaptive-navigation, which arranges components rather than reviewing them.
Construct
new(ident: impl Into<Ident>, build: impl FnOnce(ContainerSize, &mut Window, &mut App) -> AnyElement + 'static) -> Self
Options
Chain onto the value.
fill() -> Self
Example · responsive
fn responsive(_window: &mut Window, cx: &mut App) -> AnyElement {
let theme = cx.theme().clone();
let arrangement = move |id: &'static str| {
let theme = theme.clone();
move |size: ContainerSize, _: &mut Window, cx: &mut App| {
let wide = size.width().is_some_and(|measured| measured >= 480.0);
// The unmeasured frame is a real state and it is named rather than
// guessed, which is the whole argument the component makes.
let heading = match size.width() {
None => "Not laid out yet, so neither arrangement is chosen".to_string(),
Some(measured) if wide => format!("{measured:.0}px wide, so two columns"),
Some(measured) => format!("{measured:.0}px wide, so one column"),
};
// A card rather than a tinted rectangle, and with enough in it to
// be a column: two panels holding one word each say nothing about
// an arrangement, which is what the component is for.
let block = |label: &'static str, lines: &'static [&'static str], cx: &mut App| {
div()
.flex_1()
.min_w_0()
.column()
.gap_token(&theme, Space::Xs)
.p_token(&theme, Space::Md)
.card_surface(&theme, CardVariant::Elevated)
.child(crate::foundation::text(&theme, TypeScale::Label, label))
.children(lines.iter().map(|line| {
crate::foundation::text(&theme, TypeScale::Caption, *line)
.text_tone(&theme, TextTone::Muted)
}))
.semantic_in(
cx,
NodeSpec::new(format!("{id}.{}", label.to_lowercase()), Role::Group)
.text(label),
)
};
let panes = div()
.gap_token(&theme, Space::Sm)
.child(block(
"Settings",
&["Theme", "Density", "Reading direction"],
cx,
))
.child(block(
"Detail",
&["Studio Dark", "Comfortable", "Left to right"],
cx,
));
div()
.column()
.gap_token(&theme, Space::Sm)
.child(
crate::foundation::text(&theme, TypeScale::Caption, heading.clone())
.text_tone(&theme, TextTone::Faint),
)
.child(if wide { panes.row() } else { panes.column() })
.semantic_in(cx, NodeSpec::new(id, Role::Group).value(heading))
.into_any_element()
}
};
let theme = cx.theme().clone();
stack(&theme)
.child(caption(&theme, "Wide enough for two columns"))
.child(div().w(px(620.0)).child(Responsive::new(
"scene.responsive.wide",
arrangement("scene.responsive.wide.body"),
)))
.child(caption(&theme, "The same content, narrow"))
.child(div().w(px(320.0)).child(Responsive::new(
"scene.responsive.narrow",
arrangement("scene.responsive.narrow.body"),
)))
.into_any_element()
}