diff --git a/crates/component/src/button/button.rs b/crates/component/src/button/button.rs index 40785496ea..c924635280 100644 --- a/crates/component/src/button/button.rs +++ b/crates/component/src/button/button.rs @@ -574,6 +574,13 @@ impl RenderOnce for Button { let mut base = self.base; let children = self.children; let instance_style = base.style().clone(); + // The content row fills the root, so alignment set on the button only + // reaches what it shows when the row repeats it. + let content_alignment = StyleRefinement { + justify_content: instance_style.justify_content, + align_items: instance_style.align_items, + ..Default::default() + }; let normal_style = style.normal(self.outline, cx); let selected_style = style.selected(self.outline, cx); let disabled_style = style.disabled(self.outline, cx); @@ -710,6 +717,7 @@ impl RenderOnce for Button { _ => this.gap_2(), }) .refine_style(&self.content_style) + .refine_style(&content_alignment) .when_some(self.icon, |this, icon| { this.child( icon.loading_icon(self.loading_icon) @@ -1731,6 +1739,75 @@ mod tests { assert!(bounds.size.height > px(0.)); } + /// A row-shaped button — full width, content on the left — is built by + /// asking the button for `justify_start`. The content row fills the root, + /// so the request has to reach that row, or every such row is centered. + #[gpui::test] + fn full_width_button_places_content_by_its_alignment(cx: &mut gpui::TestAppContext) { + use gpui::{Context, Render}; + + struct AlignmentHarness; + + fn row(id: &'static str) -> Button { + Button::new(id).ghost().w_full().child( + div() + .debug_selector(move || format!("{id}-content")) + .child("Row"), + ) + } + + impl Render for AlignmentHarness { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + div() + .w(px(400.)) + .child( + row("start") + .justify_start() + .debug_selector(|| "start".into()), + ) + .child(row("centered").debug_selector(|| "centered".into())) + .child( + row("top") + .h(px(60.)) + .items_start() + .debug_selector(|| "top".into()), + ) + } + } + + cx.update(crate::init); + let (_, cx) = cx.add_window_view(|_, _| AlignmentHarness); + cx.update(|window, cx| window.draw(cx).clear(cx)); + let mut bounds = |selector: &'static str| { + cx.debug_bounds(selector) + .unwrap_or_else(|| panic!("{selector} must be laid out")) + }; + + let (start, start_content) = (bounds("start"), bounds("start-content")); + assert_eq!(start.size.width, px(400.)); + assert!( + start_content.left() - start.left() < px(16.), + "justify_start must put the content at the button's leading padding, \ + got {start_content:?} in {start:?}" + ); + + let (centered, centered_content) = (bounds("centered"), bounds("centered-content")); + let leading = centered_content.left() - centered.left(); + let trailing = centered.right() - centered_content.right(); + assert!( + leading > px(100.) && (leading - trailing).abs() < px(1.), + "a button asked for nothing keeps its content centered, \ + got {centered_content:?} in {centered:?}" + ); + + let (top, top_content) = (bounds("top"), bounds("top-content")); + assert_eq!( + top_content.top(), + top.top(), + "items_start must put the content at the button's top edge" + ); + } + #[gpui::test] fn test_outline_selected_uses_outline_active_style(cx: &mut gpui::TestAppContext) { cx.update(crate::init); diff --git a/website/component/button.md b/website/component/button.md index bf6fded231..2c70ee5a59 100644 --- a/website/component/button.md +++ b/website/component/button.md @@ -363,6 +363,28 @@ Button::new("btn") ) ``` +### Content Alignment + +A button centers its content. Alignment set on the button places the content +instead, so a full-width row keeps its label and children on the leading edge: + +```rust +Button::new("row") + .ghost() + .w_full() + .h_auto() + .justify_start() + .child( + v_flex() + .items_start() + .child("Pull requests") + .child("GitHub"), + ) +``` + +`items_start()` and `items_end()` likewise place the content vertically in a +button taller than it. + [Button]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.Button.html [ButtonGroup]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonGroup.html [ButtonCustomVariant]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonCustomVariant.html diff --git a/website/zh-CN/component/button.md b/website/zh-CN/component/button.md index a753b967ce..c0c4ef8c4b 100644 --- a/website/zh-CN/component/button.md +++ b/website/zh-CN/component/button.md @@ -323,6 +323,26 @@ Button::new("btn") ) ``` +### 内容对齐 + +按钮默认将内容居中。在按钮上设置的对齐方式会作用于内容本身,因此整行宽度的按钮也能让标签和子内容靠起始边排列: + +```rust +Button::new("row") + .ghost() + .w_full() + .h_auto() + .justify_start() + .child( + v_flex() + .items_start() + .child("Pull requests") + .child("GitHub"), + ) +``` + +同样,当按钮比内容高时,`items_start()` 和 `items_end()` 决定内容在垂直方向上的位置。 + [Button]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.Button.html [ButtonGroup]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonGroup.html [ButtonCustomVariant]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonCustomVariant.html