Improve styling of tabs

* Enforce a min width per tab
* Center the title within tab, regardless of icon
* Render icon over the top of the tab title
* Ensure there is always a fixed minimum amount of filler to the right of all tabs

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-04-14 15:14:40 -07:00
parent 36699dc095
commit 3f71867af8
6 changed files with 95 additions and 45 deletions

View file

@ -3,7 +3,7 @@ use crate::{
LayoutContext, PaintContext, SizeConstraint, LayoutContext, PaintContext, SizeConstraint,
}; };
use json::ToJson; use json::ToJson;
use pathfinder_geometry::vector::{vec2f, Vector2F}; use pathfinder_geometry::vector::Vector2F;
use serde_json::json; use serde_json::json;
pub struct Align { pub struct Align {
@ -19,8 +19,13 @@ impl Align {
} }
} }
pub fn top_center(mut self) -> Self { pub fn top(mut self) -> Self {
self.alignment = vec2f(0.0, -1.0); self.alignment.set_y(-1.0);
self
}
pub fn right(mut self) -> Self {
self.alignment.set_x(1.0);
self self
} }
} }

View file

@ -23,6 +23,11 @@ impl ConstrainedBox {
} }
} }
pub fn with_min_width(mut self, min_width: f32) -> Self {
self.constraint.min.set_x(min_width);
self
}
pub fn with_max_width(mut self, max_width: f32) -> Self { pub fn with_max_width(mut self, max_width: f32) -> Self {
self.constraint.max.set_x(max_width); self.constraint.max.set_x(max_width);
self self
@ -33,6 +38,12 @@ impl ConstrainedBox {
self self
} }
pub fn with_width(mut self, width: f32) -> Self {
self.constraint.min.set_x(width);
self.constraint.max.set_x(width);
self
}
pub fn with_height(mut self, height: f32) -> Self { pub fn with_height(mut self, height: f32) -> Self {
self.constraint.min.set_y(height); self.constraint.min.set_y(height);
self.constraint.max.set_y(height); self.constraint.max.set_y(height);
@ -51,6 +62,7 @@ impl Element for ConstrainedBox {
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
constraint.min = constraint.min.max(self.constraint.min); constraint.min = constraint.min.max(self.constraint.min);
constraint.max = constraint.max.min(self.constraint.max); constraint.max = constraint.max.min(self.constraint.max);
constraint.max = constraint.max.max(constraint.min);
let size = self.child.layout(constraint, ctx); let size = self.child.layout(constraint, ctx);
(size, ()) (size, ())
} }

View file

@ -43,6 +43,18 @@ impl Container {
self self
} }
pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
self.padding.left = padding;
self.padding.right = padding;
self
}
pub fn with_vertical_padding(mut self, padding: f32) -> Self {
self.padding.top = padding;
self.padding.bottom = padding;
self
}
pub fn with_uniform_padding(mut self, padding: f32) -> Self { pub fn with_uniform_padding(mut self, padding: f32) -> Self {
self.padding = Padding { self.padding = Padding {
top: padding, top: padding,

View file

@ -1,4 +1,4 @@
use std::any::Any; use std::{any::Any, f32::INFINITY};
use crate::{ use crate::{
json::{self, ToJson, Value}, json::{self, ToJson, Value},
@ -88,9 +88,13 @@ impl Element for Flex {
let mut remaining_space = constraint.max_along(self.axis) - fixed_space; let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
let mut remaining_flex = total_flex; let mut remaining_flex = total_flex;
for child in &mut self.children { for child in &mut self.children {
let space_per_flex = remaining_space / remaining_flex;
if let Some(flex) = Self::child_flex(&child) { if let Some(flex) = Self::child_flex(&child) {
let child_max = space_per_flex * flex; let child_max = if remaining_flex == 0.0 {
remaining_space
} else {
let space_per_flex = remaining_space / remaining_flex;
space_per_flex * flex
};
let child_constraint = match self.axis { let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new( Axis::Horizontal => SizeConstraint::new(
vec2f(0.0, constraint.min.y()), vec2f(0.0, constraint.min.y()),

View file

@ -77,7 +77,7 @@ impl View for FileFinder {
.with_max_height(400.0) .with_max_height(400.0)
.boxed(), .boxed(),
) )
.top_center() .top()
.named("file finder") .named("file finder")
} }

View file

@ -192,33 +192,28 @@ impl Pane {
let padding = 6.; let padding = 6.;
let mut container = Container::new( let mut container = Container::new(
Align::new( Stack::new()
Flex::row() .with_child(
.with_child( Align::new(
Label::new(title, settings.ui_font_family, settings.ui_font_size) Label::new(title, settings.ui_font_family, settings.ui_font_size)
.boxed(), .boxed(),
) )
.with_child( .boxed(),
Container::new( )
LineBox::new( .with_child(
settings.ui_font_family, LineBox::new(
settings.ui_font_size, settings.ui_font_family,
ConstrainedBox::new(Self::render_modified_icon( settings.ui_font_size,
item.is_dirty(app), Align::new(Self::render_modified_icon(item.is_dirty(app)))
)) .right()
.with_max_width(12.)
.boxed(),
)
.boxed(), .boxed(),
)
.with_margin_left(20.)
.boxed(),
) )
.boxed(), .boxed(),
) )
.boxed(), .boxed(),
) )
.with_uniform_padding(padding) .with_vertical_padding(padding)
.with_horizontal_padding(10.)
.with_border(border); .with_border(border);
if ix == self.active_item { if ix == self.active_item {
@ -240,6 +235,7 @@ impl Pane {
}) })
.boxed(), .boxed(),
) )
.with_min_width(80.0)
.with_max_width(264.0) .with_max_width(264.0)
.boxed(), .boxed(),
) )
@ -247,9 +243,29 @@ impl Pane {
); );
} }
// Ensure there's always a minimum amount of space after the last tab,
// so that the tab's border doesn't abut the window's border.
row.add_child(
ConstrainedBox::new(
Container::new(
LineBox::new(
settings.ui_font_family,
settings.ui_font_size,
Empty::new().boxed(),
)
.boxed(),
)
.with_uniform_padding(6.0)
.with_border(Border::bottom(1.0, border_color))
.boxed(),
)
.with_min_width(20.)
.named("fixed-filler"),
);
row.add_child( row.add_child(
Expanded::new( Expanded::new(
1.0, 0.0,
Container::new( Container::new(
LineBox::new( LineBox::new(
settings.ui_font_family, settings.ui_font_family,
@ -269,23 +285,24 @@ impl Pane {
} }
fn render_modified_icon(is_modified: bool) -> ElementBox { fn render_modified_icon(is_modified: bool) -> ElementBox {
Canvas::new(move |bounds, ctx| { let diameter = 8.;
if is_modified { ConstrainedBox::new(
let padding = if bounds.height() < bounds.width() { Canvas::new(move |bounds, ctx| {
vec2f(bounds.width() - bounds.height(), 0.0) if is_modified {
} else { let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
vec2f(0.0, bounds.height() - bounds.width()) ctx.scene.push_quad(Quad {
}; bounds: square,
let square = RectF::new(bounds.origin() + padding / 2., bounds.size() - padding); background: Some(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()),
ctx.scene.push_quad(Quad { border: Default::default(),
bounds: square, corner_radius: diameter / 2.,
background: Some(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()), });
border: Default::default(), }
corner_radius: square.width() / 2., })
}); .boxed(),
} )
}) .with_width(diameter)
.boxed() .with_height(diameter)
.named("tab-right-icon")
} }
} }