Allow flex items to float to the end of the flex axis

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-03-30 16:38:00 +02:00
parent 621e67bca7
commit 0453dd1101
16 changed files with 138 additions and 104 deletions

View file

@ -219,7 +219,7 @@ impl ChatPanel {
Empty::new().boxed() Empty::new().boxed()
}; };
Flexible::new(1., true, messages).boxed() FlexItem::new(messages).flex(1., true).boxed()
} }
fn render_message(&self, message: &ChannelMessage, cx: &AppContext) -> ElementBox { fn render_message(&self, message: &ChannelMessage, cx: &AppContext) -> ElementBox {

View file

@ -212,7 +212,7 @@ impl ContactsPanel {
})); }));
} }
}) })
.flexible(1., true) .flex(1., true)
.boxed() .boxed()
}) })
.constrained() .constrained()

View file

@ -78,7 +78,11 @@ impl View for FileFinder {
.with_style(settings.theme.selector.input_editor.container) .with_style(settings.theme.selector.input_editor.container)
.boxed(), .boxed(),
) )
.with_child(Flexible::new(1.0, false, self.render_matches(cx)).boxed()) .with_child(
FlexItem::new(self.render_matches(cx))
.flex(1., false)
.boxed(),
)
.boxed(), .boxed(),
) )
.with_style(settings.theme.selector.container) .with_style(settings.theme.selector.container)
@ -166,23 +170,19 @@ impl FileFinder {
// .boxed(), // .boxed(),
// ) // )
.with_child( .with_child(
Flexible::new( Flex::column()
1.0, .with_child(
false, Label::new(file_name.to_string(), style.label.clone())
Flex::column() .with_highlights(file_name_positions)
.with_child( .boxed(),
Label::new(file_name.to_string(), style.label.clone()) )
.with_highlights(file_name_positions) .with_child(
.boxed(), Label::new(full_path, style.label.clone())
) .with_highlights(full_path_positions)
.with_child( .boxed(),
Label::new(full_path, style.label.clone()) )
.with_highlights(full_path_positions) .flex(1., false)
.boxed(), .boxed(),
)
.boxed(),
)
.boxed(),
) )
.boxed(), .boxed(),
) )

View file

@ -139,11 +139,18 @@ pub trait Element {
Expanded::new(self.boxed()) Expanded::new(self.boxed())
} }
fn flexible(self, flex: f32, expanded: bool) -> Flexible fn flex(self, flex: f32, expanded: bool) -> FlexItem
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Flexible::new(flex, expanded, self.boxed()) FlexItem::new(self.boxed()).flex(flex, expanded)
}
fn flex_float(self) -> FlexItem
where
Self: 'static + Sized,
{
FlexItem::new(self.boxed()).float()
} }
} }

View file

@ -34,7 +34,7 @@ impl Flex {
fn layout_flex_children( fn layout_flex_children(
&mut self, &mut self,
expanded: bool, layout_expanded: bool,
constraint: SizeConstraint, constraint: SizeConstraint,
remaining_space: &mut f32, remaining_space: &mut f32,
remaining_flex: &mut f32, remaining_flex: &mut f32,
@ -44,32 +44,33 @@ impl Flex {
let cross_axis = self.axis.invert(); let cross_axis = self.axis.invert();
for child in &mut self.children { for child in &mut self.children {
if let Some(metadata) = child.metadata::<FlexParentData>() { if let Some(metadata) = child.metadata::<FlexParentData>() {
if metadata.expanded != expanded { if let Some((flex, expanded)) = metadata.flex {
continue; if expanded != layout_expanded {
} continue;
}
let flex = metadata.flex; let child_max = if *remaining_flex == 0.0 {
let child_max = if *remaining_flex == 0.0 { *remaining_space
*remaining_space } else {
} else { let space_per_flex = *remaining_space / *remaining_flex;
let space_per_flex = *remaining_space / *remaining_flex; space_per_flex * flex
space_per_flex * flex };
}; let child_min = if expanded { child_max } else { 0. };
let child_min = if expanded { child_max } else { 0. }; let child_constraint = match self.axis {
let child_constraint = match self.axis { Axis::Horizontal => SizeConstraint::new(
Axis::Horizontal => SizeConstraint::new( vec2f(child_min, constraint.min.y()),
vec2f(child_min, constraint.min.y()), vec2f(child_max, constraint.max.y()),
vec2f(child_max, constraint.max.y()), ),
), Axis::Vertical => SizeConstraint::new(
Axis::Vertical => SizeConstraint::new( vec2f(constraint.min.x(), child_min),
vec2f(constraint.min.x(), child_min), vec2f(constraint.max.x(), child_max),
vec2f(constraint.max.x(), child_max), ),
), };
}; let child_size = child.layout(child_constraint, cx);
let child_size = child.layout(child_constraint, cx); *remaining_space -= child_size.along(self.axis);
*remaining_space -= child_size.along(self.axis); *remaining_flex -= flex;
*remaining_flex -= flex; *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); }
} }
} }
} }
@ -82,7 +83,7 @@ impl Extend<ElementBox> for Flex {
} }
impl Element for Flex { impl Element for Flex {
type LayoutState = bool; type LayoutState = f32;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
@ -96,8 +97,11 @@ impl Element for Flex {
let cross_axis = self.axis.invert(); let cross_axis = self.axis.invert();
let mut cross_axis_max: f32 = 0.0; let mut cross_axis_max: f32 = 0.0;
for child in &mut self.children { for child in &mut self.children {
if let Some(metadata) = child.metadata::<FlexParentData>() { if let Some(flex) = child
*total_flex.get_or_insert(0.) += metadata.flex; .metadata::<FlexParentData>()
.and_then(|metadata| metadata.flex.map(|(flex, _)| flex))
{
*total_flex.get_or_insert(0.) += flex;
} else { } else {
let child_constraint = match self.axis { let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new( Axis::Horizontal => SizeConstraint::new(
@ -115,12 +119,12 @@ impl Element for Flex {
} }
} }
let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
let mut size = if let Some(mut remaining_flex) = total_flex { let mut size = if let Some(mut remaining_flex) = total_flex {
if constraint.max_along(self.axis).is_infinite() { if remaining_space.is_infinite() {
panic!("flex contains flexible children but has an infinite constraint along the flex axis"); panic!("flex contains flexible children but has an infinite constraint along the flex axis");
} }
let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
self.layout_flex_children( self.layout_flex_children(
false, false,
constraint, constraint,
@ -156,38 +160,47 @@ impl Element for Flex {
size.set_y(size.y().max(constraint.min.y())); size.set_y(size.y().max(constraint.min.y()));
} }
let mut overflowing = false;
if size.x() > constraint.max.x() { if size.x() > constraint.max.x() {
size.set_x(constraint.max.x()); size.set_x(constraint.max.x());
overflowing = true;
} }
if size.y() > constraint.max.y() { if size.y() > constraint.max.y() {
size.set_y(constraint.max.y()); size.set_y(constraint.max.y());
overflowing = true;
} }
(size, overflowing) (size, remaining_space)
} }
fn paint( fn paint(
&mut self, &mut self,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
overflowing: &mut Self::LayoutState, remaining_space: &mut Self::LayoutState,
cx: &mut PaintContext, cx: &mut PaintContext,
) -> Self::PaintState { ) -> Self::PaintState {
if *overflowing { let overflowing = *remaining_space < 0.;
if overflowing {
cx.scene.push_layer(Some(bounds)); cx.scene.push_layer(Some(bounds));
} }
let mut child_origin = bounds.origin(); let mut child_origin = bounds.origin();
for child in &mut self.children { for child in &mut self.children {
if *remaining_space > 0. {
if let Some(metadata) = child.metadata::<FlexParentData>() {
if metadata.float {
match self.axis {
Axis::Horizontal => child_origin += vec2f(*remaining_space, 0.0),
Axis::Vertical => child_origin += vec2f(0.0, *remaining_space),
}
*remaining_space = 0.;
}
}
}
child.paint(child_origin, visible_bounds, cx); child.paint(child_origin, visible_bounds, cx);
match self.axis { match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
} }
} }
if *overflowing { if overflowing {
cx.scene.pop_layer(); cx.scene.pop_layer();
} }
} }
@ -224,25 +237,38 @@ impl Element for Flex {
} }
struct FlexParentData { struct FlexParentData {
flex: f32, flex: Option<(f32, bool)>,
expanded: bool, float: bool,
} }
pub struct Flexible { pub struct FlexItem {
metadata: FlexParentData, metadata: FlexParentData,
child: ElementBox, child: ElementBox,
} }
impl Flexible { impl FlexItem {
pub fn new(flex: f32, expanded: bool, child: ElementBox) -> Self { pub fn new(child: ElementBox) -> Self {
Flexible { FlexItem {
metadata: FlexParentData { flex, expanded }, metadata: FlexParentData {
flex: None,
float: false,
},
child, child,
} }
} }
pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
self.metadata.flex = Some((flex, expanded));
self
}
pub fn float(mut self) -> Self {
self.metadata.float = true;
self
}
} }
impl Element for Flexible { impl Element for FlexItem {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();

View file

@ -77,7 +77,11 @@ impl View for OutlineView {
.with_style(settings.theme.selector.input_editor.container) .with_style(settings.theme.selector.input_editor.container)
.boxed(), .boxed(),
) )
.with_child(Flexible::new(1.0, false, self.render_matches(cx)).boxed()) .with_child(
FlexItem::new(self.render_matches(cx))
.flex(1.0, false)
.boxed(),
)
.contained() .contained()
.with_style(settings.theme.selector.container) .with_style(settings.theme.selector.container)
.constrained() .constrained()

View file

@ -76,7 +76,11 @@ impl View for ProjectSymbolsView {
.with_style(settings.theme.selector.input_editor.container) .with_style(settings.theme.selector.input_editor.container)
.boxed(), .boxed(),
) )
.with_child(Flexible::new(1.0, false, self.render_matches(cx)).boxed()) .with_child(
FlexItem::new(self.render_matches(cx))
.flex(1., false)
.boxed(),
)
.contained() .contained()
.with_style(settings.theme.selector.container) .with_style(settings.theme.selector.container)
.constrained() .constrained()

View file

@ -82,11 +82,7 @@ impl View for BufferSearchBar {
Flex::row() Flex::row()
.with_child( .with_child(
Flex::row() Flex::row()
.with_child( .with_child(ChildView::new(&self.query_editor).flex(1., true).boxed())
ChildView::new(&self.query_editor)
.flexible(1., true)
.boxed(),
)
.with_children(self.active_editor.as_ref().and_then(|editor| { .with_children(self.active_editor.as_ref().and_then(|editor| {
let matches = self.editors_with_matches.get(&editor.downgrade())?; let matches = self.editors_with_matches.get(&editor.downgrade())?;
let message = if let Some(match_ix) = self.active_match_index { let message = if let Some(match_ix) = self.active_match_index {

View file

@ -164,12 +164,10 @@ impl View for ProjectSearchView {
.aligned() .aligned()
.contained() .contained()
.with_background_color(theme.editor.background) .with_background_color(theme.editor.background)
.flexible(1., true) .flex(1., true)
.boxed() .boxed()
} else { } else {
ChildView::new(&self.results_editor) ChildView::new(&self.results_editor).flex(1., true).boxed()
.flexible(1., true)
.boxed()
} }
} }
@ -691,11 +689,7 @@ impl View for ProjectSearchBar {
Flex::row() Flex::row()
.with_child( .with_child(
Flex::row() Flex::row()
.with_child( .with_child(ChildView::new(&search.query_editor).flex(1., true).boxed())
ChildView::new(&search.query_editor)
.flexible(1., true)
.boxed(),
)
.with_children(search.active_match_index.map(|match_ix| { .with_children(search.active_match_index.map(|match_ix| {
Label::new( Label::new(
format!( format!(

View file

@ -310,7 +310,11 @@ impl View for ThemeSelector {
.with_style(theme.selector.input_editor.container) .with_style(theme.selector.input_editor.container)
.boxed(), .boxed(),
) )
.with_child(Flexible::new(1.0, false, self.render_matches(cx)).boxed()) .with_child(
FlexItem::new(self.render_matches(cx))
.flex(1., false)
.boxed(),
)
.boxed(), .boxed(),
) )
.with_style(theme.selector.container) .with_style(theme.selector.container)

View file

@ -612,7 +612,7 @@ impl Pane {
Empty::new() Empty::new()
.contained() .contained()
.with_border(theme.workspace.tab.container.border) .with_border(theme.workspace.tab.container.border)
.flexible(0., true) .flex(0., true)
.named("filler"), .named("filler"),
); );
@ -641,7 +641,7 @@ impl View for Pane {
Flex::column() Flex::column()
.with_child(self.render_tabs(cx)) .with_child(self.render_tabs(cx))
.with_child(ChildView::new(&self.toolbar).boxed()) .with_child(ChildView::new(&self.toolbar).boxed())
.with_child(ChildView::new(active_item).flexible(1., true).boxed()) .with_child(ChildView::new(active_item).flex(1., true).boxed())
.boxed() .boxed()
} else { } else {
Empty::new().boxed() Empty::new().boxed()

View file

@ -248,7 +248,7 @@ impl PaneAxis {
member = Container::new(member).with_border(border).boxed(); member = Container::new(member).with_border(border).boxed();
} }
Flexible::new(1.0, true, member).boxed() FlexItem::new(member).flex(1.0, true).boxed()
})) }))
.boxed() .boxed()
} }

View file

@ -138,7 +138,7 @@ impl Sidebar {
let width = self.width.clone(); let width = self.width.clone();
move |size, _| *width.borrow_mut() = size.x() move |size, _| *width.borrow_mut() = size.x()
}) })
.flexible(1., false) .flex(1., false)
.boxed(), .boxed(),
); );
if matches!(self.side, Side::Left) { if matches!(self.side, Side::Left) {

View file

@ -47,12 +47,12 @@ impl View for StatusBar {
.with_margin_right(theme.item_spacing) .with_margin_right(theme.item_spacing)
.boxed() .boxed()
})) }))
.with_child(Empty::new().flexible(1., true).boxed())
.with_children(self.right_items.iter().map(|i| { .with_children(self.right_items.iter().map(|i| {
ChildView::new(i.as_ref()) ChildView::new(i.as_ref())
.aligned() .aligned()
.contained() .contained()
.with_margin_left(theme.item_spacing) .with_margin_left(theme.item_spacing)
.flex_float()
.boxed() .boxed()
})) }))
.contained() .contained()

View file

@ -46,12 +46,12 @@ impl View for Toolbar {
.with_margin_right(theme.item_spacing) .with_margin_right(theme.item_spacing)
.boxed() .boxed()
})) }))
.with_child(Empty::new().flexible(1., true).boxed())
.with_children(self.right_items.iter().map(|i| { .with_children(self.right_items.iter().map(|i| {
ChildView::new(i.as_ref()) ChildView::new(i.as_ref())
.aligned() .aligned()
.contained() .contained()
.with_margin_left(theme.item_spacing) .with_margin_left(theme.item_spacing)
.flex_float()
.boxed() .boxed()
})) }))
.contained() .contained()

View file

@ -1946,36 +1946,35 @@ impl View for Workspace {
if let Some(element) = if let Some(element) =
self.left_sidebar.render_active_item(&theme, cx) self.left_sidebar.render_active_item(&theme, cx)
{ {
content.add_child(Flexible::new(0.8, false, element).boxed()); content
.add_child(FlexItem::new(element).flex(0.8, false).boxed());
} }
content.add_child( content.add_child(
Flex::column() Flex::column()
.with_child( .with_child(
Flexible::new( FlexItem::new(self.center.render(
1., &theme,
true, &self.follower_states_by_leader,
self.center.render( self.project.read(cx).collaborators(),
&theme, ))
&self.follower_states_by_leader, .flex(1., true)
self.project.read(cx).collaborators(),
),
)
.boxed(), .boxed(),
) )
.with_child(ChildView::new(&self.status_bar).boxed()) .with_child(ChildView::new(&self.status_bar).boxed())
.flexible(1., true) .flex(1., true)
.boxed(), .boxed(),
); );
if let Some(element) = if let Some(element) =
self.right_sidebar.render_active_item(&theme, cx) self.right_sidebar.render_active_item(&theme, cx)
{ {
content.add_child(Flexible::new(0.8, false, element).boxed()); content
.add_child(FlexItem::new(element).flex(0.8, false).boxed());
} }
content.add_child(self.right_sidebar.render(&theme, cx)); content.add_child(self.right_sidebar.render(&theme, cx));
content.boxed() content.boxed()
}) })
.with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed())) .with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
.flexible(1.0, true) .flex(1.0, true)
.boxed(), .boxed(),
) )
.contained() .contained()