Allow ssh connection for setting up zed (#12063)
Co-Authored-By: Mikayla <mikayla@zed.dev> Release Notes: - Magic `ssh` login feature for remote development --------- Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
parent
3382e79ef9
commit
e5b9e2044e
29 changed files with 1242 additions and 785 deletions
|
@ -13,6 +13,7 @@ mod list;
|
|||
mod modal;
|
||||
mod popover;
|
||||
mod popover_menu;
|
||||
mod radio;
|
||||
mod right_click_menu;
|
||||
mod stack;
|
||||
mod tab;
|
||||
|
@ -39,6 +40,7 @@ pub use list::*;
|
|||
pub use modal::*;
|
||||
pub use popover::*;
|
||||
pub use popover_menu::*;
|
||||
pub use radio::*;
|
||||
pub use right_click_menu::*;
|
||||
pub use stack::*;
|
||||
pub use tab::*;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{AnyView, DefiniteLength};
|
||||
|
||||
use crate::{prelude::*, IconPosition, KeyBinding, Spacing};
|
||||
use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing};
|
||||
use crate::{
|
||||
ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
|
||||
};
|
||||
|
@ -340,6 +340,11 @@ impl ButtonCommon for Button {
|
|||
self.base = self.base.tooltip(tooltip);
|
||||
self
|
||||
}
|
||||
|
||||
fn layer(mut self, elevation: ElevationIndex) -> Self {
|
||||
self.base = self.base.layer(elevation);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Button {
|
||||
|
|
|
@ -2,7 +2,7 @@ use gpui::{relative, DefiniteLength, MouseButton};
|
|||
use gpui::{transparent_black, AnyElement, AnyView, ClickEvent, Hsla, Rems};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{prelude::*, Spacing};
|
||||
use crate::{prelude::*, Elevation, ElevationIndex, Spacing};
|
||||
|
||||
/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
|
||||
pub trait SelectableButton: Selectable {
|
||||
|
@ -33,6 +33,8 @@ pub trait ButtonCommon: Clickable + Disableable {
|
|||
/// Nearly all interactable elements should have a tooltip. Some example
|
||||
/// exceptions might a scroll bar, or a slider.
|
||||
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
|
||||
|
||||
fn layer(self, elevation: ElevationIndex) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
||||
|
@ -135,11 +137,35 @@ pub(crate) struct ButtonLikeStyles {
|
|||
pub icon_color: Hsla,
|
||||
}
|
||||
|
||||
fn element_bg_from_elevation(elevation: Option<Elevation>, cx: &mut WindowContext) -> Hsla {
|
||||
match elevation {
|
||||
Some(Elevation::ElevationIndex(ElevationIndex::Background)) => {
|
||||
cx.theme().colors().element_background
|
||||
}
|
||||
Some(Elevation::ElevationIndex(ElevationIndex::ElevatedSurface)) => {
|
||||
cx.theme().colors().surface_background
|
||||
}
|
||||
Some(Elevation::ElevationIndex(ElevationIndex::Surface)) => {
|
||||
cx.theme().colors().elevated_surface_background
|
||||
}
|
||||
Some(Elevation::ElevationIndex(ElevationIndex::ModalSurface)) => {
|
||||
cx.theme().colors().background
|
||||
}
|
||||
_ => cx.theme().colors().element_background,
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonStyle {
|
||||
pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
|
||||
pub(crate) fn enabled(
|
||||
self,
|
||||
elevation: Option<Elevation>,
|
||||
cx: &mut WindowContext,
|
||||
) -> ButtonLikeStyles {
|
||||
let filled_background = element_bg_from_elevation(elevation, cx);
|
||||
|
||||
match self {
|
||||
ButtonStyle::Filled => ButtonLikeStyles {
|
||||
background: cx.theme().colors().element_background,
|
||||
background: filled_background,
|
||||
border_color: transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
|
@ -160,10 +186,17 @@ impl ButtonStyle {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
|
||||
pub(crate) fn hovered(
|
||||
self,
|
||||
elevation: Option<Elevation>,
|
||||
cx: &mut WindowContext,
|
||||
) -> ButtonLikeStyles {
|
||||
let mut filled_background = element_bg_from_elevation(elevation, cx);
|
||||
filled_background.fade_out(0.92);
|
||||
|
||||
match self {
|
||||
ButtonStyle::Filled => ButtonLikeStyles {
|
||||
background: cx.theme().colors().element_hover,
|
||||
background: filled_background,
|
||||
border_color: transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
|
@ -238,7 +271,13 @@ impl ButtonStyle {
|
|||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
|
||||
pub(crate) fn disabled(
|
||||
self,
|
||||
elevation: Option<Elevation>,
|
||||
cx: &mut WindowContext,
|
||||
) -> ButtonLikeStyles {
|
||||
let filled_background = element_bg_from_elevation(elevation, cx).fade_out(0.82);
|
||||
|
||||
match self {
|
||||
ButtonStyle::Filled => ButtonLikeStyles {
|
||||
background: cx.theme().colors().element_disabled,
|
||||
|
@ -301,6 +340,7 @@ pub struct ButtonLike {
|
|||
pub(super) selected_style: Option<ButtonStyle>,
|
||||
pub(super) width: Option<DefiniteLength>,
|
||||
pub(super) height: Option<DefiniteLength>,
|
||||
pub(super) layer: Option<Elevation>,
|
||||
size: ButtonSize,
|
||||
rounding: Option<ButtonLikeRounding>,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
|
@ -324,6 +364,7 @@ impl ButtonLike {
|
|||
tooltip: None,
|
||||
children: SmallVec::new(),
|
||||
on_click: None,
|
||||
layer: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,6 +438,11 @@ impl ButtonCommon for ButtonLike {
|
|||
self.tooltip = Some(Box::new(tooltip));
|
||||
self
|
||||
}
|
||||
|
||||
fn layer(mut self, elevation: ElevationIndex) -> Self {
|
||||
self.layer = Some(elevation.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl VisibleOnHover for ButtonLike {
|
||||
|
@ -437,11 +483,11 @@ impl RenderOnce for ButtonLike {
|
|||
ButtonSize::Default | ButtonSize::Compact => this.px(Spacing::Small.rems(cx)),
|
||||
ButtonSize::None => this,
|
||||
})
|
||||
.bg(style.enabled(cx).background)
|
||||
.bg(style.enabled(self.layer, cx).background)
|
||||
.when(self.disabled, |this| this.cursor_not_allowed())
|
||||
.when(!self.disabled, |this| {
|
||||
this.cursor_pointer()
|
||||
.hover(|hover| hover.bg(style.hovered(cx).background))
|
||||
.hover(|hover| hover.bg(style.hovered(self.layer, cx).background))
|
||||
.active(|active| active.bg(style.active(cx).background))
|
||||
})
|
||||
.when_some(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{AnyView, DefiniteLength};
|
||||
|
||||
use crate::{prelude::*, SelectableButton, Spacing};
|
||||
use crate::{prelude::*, ElevationIndex, SelectableButton, Spacing};
|
||||
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize};
|
||||
|
||||
use super::button_icon::ButtonIcon;
|
||||
|
@ -119,6 +119,11 @@ impl ButtonCommon for IconButton {
|
|||
self.base = self.base.tooltip(tooltip);
|
||||
self
|
||||
}
|
||||
|
||||
fn layer(mut self, elevation: ElevationIndex) -> Self {
|
||||
self.base = self.base.layer(elevation);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl VisibleOnHover for IconButton {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{AnyView, ClickEvent};
|
||||
|
||||
use crate::{prelude::*, ButtonLike, ButtonLikeRounding};
|
||||
use crate::{prelude::*, ButtonLike, ButtonLikeRounding, ElevationIndex};
|
||||
|
||||
/// The position of a [`ToggleButton`] within a group of buttons.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
|
@ -103,6 +103,11 @@ impl ButtonCommon for ToggleButton {
|
|||
self.base = self.base.tooltip(tooltip);
|
||||
self
|
||||
}
|
||||
|
||||
fn layer(mut self, elevation: ElevationIndex) -> Self {
|
||||
self.base = self.base.layer(elevation);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ToggleButton {
|
||||
|
|
|
@ -1,29 +1,121 @@
|
|||
use crate::{
|
||||
h_flex, rems_from_px, v_flex, Clickable, Color, Headline, HeadlineSize, IconButton,
|
||||
IconButtonShape, IconName, Label, LabelCommon, LabelSize, Spacing,
|
||||
};
|
||||
use gpui::{prelude::FluentBuilder, *};
|
||||
use smallvec::SmallVec;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
use crate::{
|
||||
h_flex, Clickable, IconButton, IconButtonShape, IconName, Label, LabelCommon, LabelSize,
|
||||
Spacing,
|
||||
};
|
||||
#[derive(IntoElement)]
|
||||
pub struct Modal {
|
||||
id: ElementId,
|
||||
header: ModalHeader,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
footer: Option<ModalFooter>,
|
||||
container_id: ElementId,
|
||||
container_scroll_handler: Option<ScrollHandle>,
|
||||
}
|
||||
|
||||
impl Modal {
|
||||
pub fn new(id: impl Into<SharedString>, scroll_handle: Option<ScrollHandle>) -> Self {
|
||||
let id = id.into();
|
||||
|
||||
let container_id = ElementId::Name(format!("{}_container", id.clone()).into());
|
||||
Self {
|
||||
id: ElementId::Name(id),
|
||||
header: ModalHeader::new(),
|
||||
children: SmallVec::new(),
|
||||
footer: None,
|
||||
container_id,
|
||||
container_scroll_handler: scroll_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header(mut self, header: ModalHeader) -> Self {
|
||||
self.header = header;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn section(mut self, section: Section) -> Self {
|
||||
self.children.push(section.into_any_element());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn footer(mut self, footer: ModalFooter) -> Self {
|
||||
self.footer = Some(footer);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_dismiss(mut self, show: bool) -> Self {
|
||||
self.header.show_dismiss_button = show;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_back(mut self, show: bool) -> Self {
|
||||
self.header.show_back_button = show;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Modal {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Modal {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id(self.id.clone())
|
||||
.size_full()
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.child(self.header)
|
||||
.child(
|
||||
v_flex()
|
||||
.id(self.container_id.clone())
|
||||
.w_full()
|
||||
.gap(Spacing::Large.rems(cx))
|
||||
.when_some(
|
||||
self.container_scroll_handler,
|
||||
|this, container_scroll_handle| {
|
||||
this.overflow_y_scroll()
|
||||
.track_scroll(&container_scroll_handle)
|
||||
},
|
||||
)
|
||||
.children(self.children),
|
||||
)
|
||||
.children(self.footer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModalHeader {
|
||||
id: ElementId,
|
||||
headline: Option<SharedString>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
show_dismiss_button: bool,
|
||||
show_back_button: bool,
|
||||
}
|
||||
|
||||
impl ModalHeader {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
headline: None,
|
||||
children: SmallVec::new(),
|
||||
show_dismiss_button: false,
|
||||
show_back_button: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the headline of the modal.
|
||||
///
|
||||
/// This will insert the headline as the first item
|
||||
/// of `children` if it is not already present.
|
||||
pub fn headline(mut self, headline: impl Into<SharedString>) -> Self {
|
||||
self.headline = Some(headline.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_dismiss_button(mut self, show: bool) -> Self {
|
||||
self.show_dismiss_button = show;
|
||||
self
|
||||
|
@ -43,24 +135,36 @@ impl ParentElement for ModalHeader {
|
|||
|
||||
impl RenderOnce for ModalHeader {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let mut children = self.children;
|
||||
|
||||
if self.headline.is_some() {
|
||||
children.insert(
|
||||
0,
|
||||
Headline::new(self.headline.unwrap())
|
||||
.size(HeadlineSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
.flex_none()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.px(Spacing::Large.rems(cx))
|
||||
.py_1p5()
|
||||
.px(Spacing::XLarge.rems(cx))
|
||||
.pt(Spacing::Large.rems(cx))
|
||||
.pb(Spacing::Small.rems(cx))
|
||||
.gap(Spacing::Large.rems(cx))
|
||||
.when(self.show_back_button, |this| {
|
||||
this.child(
|
||||
div().pr_1().child(
|
||||
IconButton::new("back", IconName::ArrowLeft)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(menu::Cancel.boxed_clone());
|
||||
}),
|
||||
),
|
||||
IconButton::new("back", IconName::ArrowLeft)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(menu::Cancel.boxed_clone());
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(div().flex_1().children(self.children))
|
||||
.justify_between()
|
||||
.child(div().flex_1().children(children))
|
||||
.when(self.show_dismiss_button, |this| {
|
||||
this.child(
|
||||
IconButton::new("dismiss", IconName::Close)
|
||||
|
@ -73,31 +177,6 @@ impl RenderOnce for ModalHeader {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModalContent {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ModalContent {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ModalContent {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ModalContent {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex().w_full().px_2().py_1p5().children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModalRow {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
|
@ -123,6 +202,136 @@ impl RenderOnce for ModalRow {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModalFooter {
|
||||
start_slot: Option<AnyElement>,
|
||||
end_slot: Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl ModalFooter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
start_slot: None,
|
||||
end_slot: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_slot<E: IntoElement>(mut self, start_slot: impl Into<Option<E>>) -> Self {
|
||||
self.start_slot = start_slot.into().map(IntoElement::into_any_element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn end_slot<E: IntoElement>(mut self, end_slot: impl Into<Option<E>>) -> Self {
|
||||
self.end_slot = end_slot.into().map(IntoElement::into_any_element);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ModalFooter {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.flex_none()
|
||||
.w_full()
|
||||
.p(Spacing::Large.rems(cx))
|
||||
.justify_between()
|
||||
.child(div().when_some(self.start_slot, |this, start_slot| this.child(start_slot)))
|
||||
.child(div().when_some(self.end_slot, |this, end_slot| this.child(end_slot)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Section {
|
||||
contained: bool,
|
||||
header: Option<SectionHeader>,
|
||||
meta: Option<SharedString>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
contained: false,
|
||||
header: None,
|
||||
meta: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_contained() -> Self {
|
||||
Self {
|
||||
contained: true,
|
||||
header: None,
|
||||
meta: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contained(mut self, contained: bool) -> Self {
|
||||
self.contained = contained;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, header: SectionHeader) -> Self {
|
||||
self.header = Some(header);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
|
||||
self.meta = Some(meta.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Section {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Section {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let mut section_bg = cx.theme().colors().text;
|
||||
section_bg.fade_out(0.96);
|
||||
|
||||
let children = if self.contained {
|
||||
v_flex().flex_1().px(Spacing::XLarge.rems(cx)).child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(section_bg)
|
||||
.py(Spacing::Medium.rems(cx))
|
||||
.px(Spacing::Large.rems(cx) - rems_from_px(1.0))
|
||||
.gap_y(Spacing::Small.rems(cx))
|
||||
.child(div().flex().flex_1().size_full().children(self.children)),
|
||||
)
|
||||
} else {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_y(Spacing::Small.rems(cx))
|
||||
.px(Spacing::Large.rems(cx) + Spacing::Large.rems(cx))
|
||||
.children(self.children)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.flex_1()
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_none()
|
||||
.px(Spacing::XLarge.rems(cx))
|
||||
.children(self.header)
|
||||
.when_some(self.meta, |this, meta| {
|
||||
this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
|
||||
}),
|
||||
)
|
||||
.child(children)
|
||||
// fill any leftover space
|
||||
.child(div().flex().flex_1())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct SectionHeader {
|
||||
/// The label of the header.
|
||||
|
@ -147,23 +356,40 @@ impl SectionHeader {
|
|||
}
|
||||
|
||||
impl RenderOnce for SectionHeader {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex().id(self.label.clone()).w_full().child(
|
||||
div()
|
||||
.h_7()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(
|
||||
div().flex_1().child(
|
||||
Label::new(self.label.clone())
|
||||
.size(LabelSize::Large)
|
||||
.into_element(),
|
||||
),
|
||||
)
|
||||
.child(h_flex().children(self.end_slot)),
|
||||
)
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id(self.label.clone())
|
||||
.w_full()
|
||||
.px(Spacing::Large.rems(cx))
|
||||
.child(
|
||||
div()
|
||||
.h_7()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.gap(Spacing::Small.rems(cx))
|
||||
.child(
|
||||
div().flex_1().child(
|
||||
Label::new(self.label.clone())
|
||||
.size(LabelSize::Small)
|
||||
.into_element(),
|
||||
),
|
||||
)
|
||||
.child(h_flex().children(self.end_slot)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<SectionHeader> for SharedString {
|
||||
fn into(self) -> SectionHeader {
|
||||
SectionHeader::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<SectionHeader> for &'static str {
|
||||
fn into(self) -> SectionHeader {
|
||||
let label: SharedString = self.into();
|
||||
SectionHeader::new(label)
|
||||
}
|
||||
}
|
||||
|
|
61
crates/ui/src/components/radio.rs
Normal file
61
crates/ui/src/components/radio.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A [`Checkbox`] that has a [`Label`].
|
||||
#[derive(IntoElement)]
|
||||
pub struct RadioWithLabel {
|
||||
id: ElementId,
|
||||
label: Label,
|
||||
selected: bool,
|
||||
on_click: Arc<dyn Fn(&bool, &mut WindowContext) + 'static>,
|
||||
}
|
||||
|
||||
impl RadioWithLabel {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
label: Label,
|
||||
selected: bool,
|
||||
on_click: impl Fn(&bool, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label,
|
||||
selected,
|
||||
on_click: Arc::new(on_click),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for RadioWithLabel {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let inner_diameter = rems_from_px(6.);
|
||||
let outer_diameter = rems_from_px(16.);
|
||||
let border_width = rems_from_px(1.);
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
.gap(Spacing::Large.rems(cx))
|
||||
.group("")
|
||||
.child(
|
||||
div()
|
||||
.size(outer_diameter)
|
||||
.rounded(outer_diameter / 2.)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.border(border_width)
|
||||
.group_hover("", |el| el.bg(cx.theme().colors().element_hover))
|
||||
.when(self.selected, |el| {
|
||||
el.child(
|
||||
div()
|
||||
.m((outer_diameter - inner_diameter) / 2. - border_width)
|
||||
.size(inner_diameter)
|
||||
.rounded(inner_diameter / 2.)
|
||||
.bg(cx.theme().colors().icon_accent),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(self.label)
|
||||
.on_click(move |_event, cx| {
|
||||
(self.on_click)(&true, cx);
|
||||
})
|
||||
}
|
||||
}
|
|
@ -9,6 +9,12 @@ pub enum Elevation {
|
|||
ElementIndex(ElementIndex),
|
||||
}
|
||||
|
||||
impl Into<Elevation> for ElevationIndex {
|
||||
fn into(self) -> Elevation {
|
||||
Elevation::ElevationIndex(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ElevationIndex {
|
||||
Background,
|
||||
|
|
|
@ -4,7 +4,7 @@ use gpui::{
|
|||
use settings::Settings;
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
|
||||
use crate::rems_from_px;
|
||||
use crate::{rems_from_px, Color};
|
||||
|
||||
/// Extends [`gpui::Styled`] with typography-related styling methods.
|
||||
pub trait StyledTypography: Styled + Sized {
|
||||
|
@ -164,6 +164,7 @@ impl HeadlineSize {
|
|||
pub struct Headline {
|
||||
size: HeadlineSize,
|
||||
text: SharedString,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl RenderOnce for Headline {
|
||||
|
@ -184,6 +185,7 @@ impl Headline {
|
|||
Self {
|
||||
size: HeadlineSize::default(),
|
||||
text: text.into(),
|
||||
color: Color::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,4 +193,9 @@ impl Headline {
|
|||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue