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:
parent
36699dc095
commit
3f71867af8
6 changed files with 95 additions and 45 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, ())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue