debugger: First slight pass at UI (#27034)
- Collapse Launch and Attach into a single split button - Fix code actions indicator being colored red. Release Notes: - N/A
This commit is contained in:
parent
73ac3d9a99
commit
c042a02cf4
7 changed files with 229 additions and 158 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5680,7 +5680,6 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
|
||||||
"strum",
|
"strum",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"theme",
|
"theme",
|
||||||
|
|
|
@ -7,20 +7,39 @@ use settings::Settings as _;
|
||||||
use task::TCPHost;
|
use task::TCPHost;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, relative, v_flex, ActiveTheme as _, Button, ButtonCommon, ButtonStyle, Clickable,
|
h_flex, relative, v_flex, ActiveTheme as _, ButtonLike, Clickable, Context, ContextMenu,
|
||||||
Context, ContextMenu, Disableable, DropdownMenu, InteractiveElement, IntoElement,
|
Disableable, Disclosure, DropdownMenu, FluentBuilder, InteractiveElement, IntoElement, Label,
|
||||||
ParentElement, Render, SharedString, Styled, Window,
|
LabelCommon, LabelSize, ParentElement, PopoverMenu, PopoverMenuHandle, Render, SharedString,
|
||||||
|
SplitButton, Styled, Window,
|
||||||
};
|
};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::attach_modal::AttachModal;
|
use crate::attach_modal::AttachModal;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||||
|
enum SpawnMode {
|
||||||
|
#[default]
|
||||||
|
Launch,
|
||||||
|
Attach,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpawnMode {
|
||||||
|
fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SpawnMode::Launch => "Launch",
|
||||||
|
SpawnMode::Attach => "Attach",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct InertState {
|
pub(crate) struct InertState {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
selected_debugger: Option<SharedString>,
|
selected_debugger: Option<SharedString>,
|
||||||
program_editor: Entity<Editor>,
|
program_editor: Entity<Editor>,
|
||||||
cwd_editor: Entity<Editor>,
|
cwd_editor: Entity<Editor>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
spawn_mode: SpawnMode,
|
||||||
|
popover_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InertState {
|
impl InertState {
|
||||||
|
@ -47,6 +66,8 @@ impl InertState {
|
||||||
program_editor,
|
program_editor,
|
||||||
selected_debugger: None,
|
selected_debugger: None,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
|
spawn_mode: SpawnMode::default(),
|
||||||
|
popover_handle: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,19 +93,46 @@ impl Render for InertState {
|
||||||
) -> impl ui::IntoElement {
|
) -> impl ui::IntoElement {
|
||||||
let weak = cx.weak_entity();
|
let weak = cx.weak_entity();
|
||||||
let disable_buttons = self.selected_debugger.is_none();
|
let disable_buttons = self.selected_debugger.is_none();
|
||||||
|
let spawn_button = ButtonLike::new_rounded_left("spawn-debug-session")
|
||||||
|
.child(Label::new(self.spawn_mode.label()).size(LabelSize::Small))
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
if this.spawn_mode == SpawnMode::Launch {
|
||||||
|
let program = this.program_editor.read(cx).text(cx);
|
||||||
|
let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx));
|
||||||
|
let kind =
|
||||||
|
kind_for_label(this.selected_debugger.as_deref().unwrap_or_else(|| {
|
||||||
|
unimplemented!(
|
||||||
|
"Automatic selection of a debugger based on users project"
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
cx.emit(InertEvent::Spawned {
|
||||||
|
config: DebugAdapterConfig {
|
||||||
|
label: "hard coded".into(),
|
||||||
|
kind,
|
||||||
|
request: DebugRequestType::Launch,
|
||||||
|
program: Some(program),
|
||||||
|
cwd: Some(cwd),
|
||||||
|
initialize_args: None,
|
||||||
|
supports_attach: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.attach(window, cx)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.disabled(disable_buttons);
|
||||||
v_flex()
|
v_flex()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.p_2()
|
.p_2()
|
||||||
|
|
||||||
.child(
|
.child(
|
||||||
v_flex().gap_1()
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
|
||||||
.child(Self::render_editor(&self.program_editor, cx))
|
.child(Self::render_editor(&self.program_editor, cx))
|
||||||
.child(
|
.child(
|
||||||
h_flex().child(DropdownMenu::new(
|
h_flex().child(DropdownMenu::new(
|
||||||
|
@ -109,45 +157,63 @@ impl Render for InertState {
|
||||||
.entry("Delve", None, setter_for_name("Delve"))
|
.entry("Delve", None, setter_for_name("Delve"))
|
||||||
.entry("LLDB", None, setter_for_name("LLDB"))
|
.entry("LLDB", None, setter_for_name("LLDB"))
|
||||||
.entry("PHP", None, setter_for_name("PHP"))
|
.entry("PHP", None, setter_for_name("PHP"))
|
||||||
.entry("JavaScript", None, setter_for_name("JavaScript"))
|
.entry(
|
||||||
|
"JavaScript",
|
||||||
|
None,
|
||||||
|
setter_for_name("JavaScript"),
|
||||||
|
)
|
||||||
.entry("Debugpy", None, setter_for_name("Debugpy"))
|
.entry("Debugpy", None, setter_for_name("Debugpy"))
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex().gap_2().child(
|
h_flex()
|
||||||
Self::render_editor(&self.cwd_editor, cx),
|
.gap_2()
|
||||||
).child(h_flex()
|
.child(Self::render_editor(&self.cwd_editor, cx))
|
||||||
.gap_4()
|
.map(|this| {
|
||||||
.pl_2()
|
let entity = cx.weak_entity();
|
||||||
.child(
|
this.child(SplitButton {
|
||||||
Button::new("launch-dap", "Launch")
|
left: spawn_button,
|
||||||
.style(ButtonStyle::Filled)
|
right: PopoverMenu::new("debugger-select-spawn-mode")
|
||||||
.disabled(disable_buttons)
|
.trigger(Disclosure::new(
|
||||||
.on_click(cx.listener(|this, _, _, cx| {
|
"debugger-spawn-button-disclosure",
|
||||||
let program = this.program_editor.read(cx).text(cx);
|
self.popover_handle.is_deployed(),
|
||||||
let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx));
|
))
|
||||||
let kind = kind_for_label(this.selected_debugger.as_deref().unwrap_or_else(|| unimplemented!("Automatic selection of a debugger based on users project")));
|
.menu(move |window, cx| {
|
||||||
cx.emit(InertEvent::Spawned {
|
Some(ContextMenu::build(window, cx, {
|
||||||
config: DebugAdapterConfig {
|
let entity = entity.clone();
|
||||||
label: "hard coded".into(),
|
move |this, _, _| {
|
||||||
kind,
|
this.entry("Launch", None, {
|
||||||
request: DebugRequestType::Launch,
|
let entity = entity.clone();
|
||||||
program: Some(program),
|
move |_, cx| {
|
||||||
cwd: Some(cwd),
|
let _ =
|
||||||
initialize_args: None,
|
entity.update(cx, |this, cx| {
|
||||||
supports_attach: false,
|
this.spawn_mode =
|
||||||
},
|
SpawnMode::Launch;
|
||||||
});
|
cx.notify();
|
||||||
})),
|
});
|
||||||
)
|
}
|
||||||
.child(Button::new("attach-dap", "Attach")
|
})
|
||||||
.style(ButtonStyle::Filled)
|
.entry("Attach", None, {
|
||||||
.disabled(disable_buttons)
|
let entity = entity.clone();
|
||||||
.on_click(cx.listener(|this, _, window, cx| this.attach(window, cx)))
|
move |_, cx| {
|
||||||
))
|
let _ =
|
||||||
)
|
entity.update(cx, |this, cx| {
|
||||||
|
this.spawn_mode =
|
||||||
|
SpawnMode::Attach;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.with_handle(self.popover_handle.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5918,11 +5918,7 @@ impl Editor {
|
||||||
breakpoint: Option<&(Anchor, Breakpoint)>,
|
breakpoint: Option<&(Anchor, Breakpoint)>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<IconButton> {
|
) -> Option<IconButton> {
|
||||||
let color = if breakpoint.is_some() {
|
let color = Color::Muted;
|
||||||
Color::Debugger
|
|
||||||
} else {
|
|
||||||
Color::Muted
|
|
||||||
};
|
|
||||||
|
|
||||||
let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
|
let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
|
||||||
let bp_kind = Arc::new(
|
let bp_kind = Arc::new(
|
||||||
|
|
|
@ -49,7 +49,6 @@ serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smallvec.workspace = true
|
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
|
|
@ -163,19 +163,18 @@ fn render_remote_button(
|
||||||
}
|
}
|
||||||
|
|
||||||
mod remote_button {
|
mod remote_button {
|
||||||
use gpui::{hsla, point, Action, AnyView, BoxShadow, ClickEvent, Corner, FocusHandle};
|
use gpui::{Action, AnyView, ClickEvent, Corner, FocusHandle};
|
||||||
use ui::{
|
use ui::{
|
||||||
div, h_flex, px, rems, ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, Clickable,
|
div, h_flex, rems, App, ButtonCommon, Clickable, ContextMenu, ElementId, FluentBuilder,
|
||||||
ContextMenu, ElementId, ElevationIndex, FluentBuilder, Icon, IconName, IconSize,
|
Icon, IconName, IconSize, IntoElement, Label, LabelCommon, LabelSize, LineHeightStyle,
|
||||||
IntoElement, Label, LabelCommon, LabelSize, LineHeightStyle, ParentElement, PopoverMenu,
|
ParentElement, PopoverMenu, SharedString, SplitButton, Styled, Tooltip, Window,
|
||||||
RenderOnce, SharedString, Styled, Tooltip, Window,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn render_fetch_button(
|
pub fn render_fetch_button(
|
||||||
keybinding_target: Option<FocusHandle>,
|
keybinding_target: Option<FocusHandle>,
|
||||||
id: SharedString,
|
id: SharedString,
|
||||||
) -> SplitButton {
|
) -> SplitButton {
|
||||||
SplitButton::new(
|
split_button(
|
||||||
id,
|
id,
|
||||||
"Fetch",
|
"Fetch",
|
||||||
0,
|
0,
|
||||||
|
@ -203,7 +202,7 @@ mod remote_button {
|
||||||
id: SharedString,
|
id: SharedString,
|
||||||
ahead: u32,
|
ahead: u32,
|
||||||
) -> SplitButton {
|
) -> SplitButton {
|
||||||
SplitButton::new(
|
split_button(
|
||||||
id,
|
id,
|
||||||
"Push",
|
"Push",
|
||||||
ahead as usize,
|
ahead as usize,
|
||||||
|
@ -232,7 +231,7 @@ mod remote_button {
|
||||||
ahead: u32,
|
ahead: u32,
|
||||||
behind: u32,
|
behind: u32,
|
||||||
) -> SplitButton {
|
) -> SplitButton {
|
||||||
SplitButton::new(
|
split_button(
|
||||||
id,
|
id,
|
||||||
"Pull",
|
"Pull",
|
||||||
ahead as usize,
|
ahead as usize,
|
||||||
|
@ -259,7 +258,7 @@ mod remote_button {
|
||||||
keybinding_target: Option<FocusHandle>,
|
keybinding_target: Option<FocusHandle>,
|
||||||
id: SharedString,
|
id: SharedString,
|
||||||
) -> SplitButton {
|
) -> SplitButton {
|
||||||
SplitButton::new(
|
split_button(
|
||||||
id,
|
id,
|
||||||
"Publish",
|
"Publish",
|
||||||
0,
|
0,
|
||||||
|
@ -286,7 +285,7 @@ mod remote_button {
|
||||||
keybinding_target: Option<FocusHandle>,
|
keybinding_target: Option<FocusHandle>,
|
||||||
id: SharedString,
|
id: SharedString,
|
||||||
) -> SplitButton {
|
) -> SplitButton {
|
||||||
SplitButton::new(
|
split_button(
|
||||||
id,
|
id,
|
||||||
"Republish",
|
"Republish",
|
||||||
0,
|
0,
|
||||||
|
@ -364,111 +363,76 @@ mod remote_button {
|
||||||
})
|
})
|
||||||
.anchor(Corner::TopRight)
|
.anchor(Corner::TopRight)
|
||||||
}
|
}
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[derive(IntoElement)]
|
fn split_button(
|
||||||
pub struct SplitButton {
|
id: SharedString,
|
||||||
pub left: ButtonLike,
|
left_label: impl Into<SharedString>,
|
||||||
pub right: AnyElement,
|
ahead_count: usize,
|
||||||
}
|
behind_count: usize,
|
||||||
|
left_icon: Option<IconName>,
|
||||||
impl SplitButton {
|
keybinding_target: Option<FocusHandle>,
|
||||||
#[allow(clippy::too_many_arguments)]
|
left_on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
fn new(
|
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
id: impl Into<SharedString>,
|
) -> SplitButton {
|
||||||
left_label: impl Into<SharedString>,
|
fn count(count: usize) -> impl IntoElement {
|
||||||
ahead_count: usize,
|
|
||||||
behind_count: usize,
|
|
||||||
left_icon: Option<IconName>,
|
|
||||||
keybinding_target: Option<FocusHandle>,
|
|
||||||
left_on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
||||||
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
) -> Self {
|
|
||||||
let id = id.into();
|
|
||||||
|
|
||||||
fn count(count: usize) -> impl IntoElement {
|
|
||||||
h_flex()
|
|
||||||
.ml_neg_px()
|
|
||||||
.h(rems(0.875))
|
|
||||||
.items_center()
|
|
||||||
.overflow_hidden()
|
|
||||||
.px_0p5()
|
|
||||||
.child(
|
|
||||||
Label::new(count.to_string())
|
|
||||||
.size(LabelSize::XSmall)
|
|
||||||
.line_height_style(LineHeightStyle::UiLabel),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let should_render_counts = left_icon.is_none() && (ahead_count > 0 || behind_count > 0);
|
|
||||||
|
|
||||||
let left = ui::ButtonLike::new_rounded_left(ElementId::Name(
|
|
||||||
format!("split-button-left-{}", id).into(),
|
|
||||||
))
|
|
||||||
.layer(ui::ElevationIndex::ModalSurface)
|
|
||||||
.size(ui::ButtonSize::Compact)
|
|
||||||
.when(should_render_counts, |this| {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.ml_neg_0p5()
|
|
||||||
.mr_1()
|
|
||||||
.when(behind_count > 0, |this| {
|
|
||||||
this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall))
|
|
||||||
.child(count(behind_count))
|
|
||||||
})
|
|
||||||
.when(ahead_count > 0, |this| {
|
|
||||||
this.child(Icon::new(IconName::ArrowUp).size(IconSize::XSmall))
|
|
||||||
.child(count(ahead_count))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when_some(left_icon, |this, left_icon| {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.ml_neg_0p5()
|
|
||||||
.mr_1()
|
|
||||||
.child(Icon::new(left_icon).size(IconSize::XSmall)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.child(Label::new(left_label).size(LabelSize::Small))
|
|
||||||
.mr_0p5(),
|
|
||||||
)
|
|
||||||
.on_click(left_on_click)
|
|
||||||
.tooltip(tooltip);
|
|
||||||
|
|
||||||
let right = render_git_action_menu(
|
|
||||||
ElementId::Name(format!("split-button-right-{}", id).into()),
|
|
||||||
keybinding_target,
|
|
||||||
)
|
|
||||||
.into_any_element();
|
|
||||||
|
|
||||||
Self { left, right }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for SplitButton {
|
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.rounded_sm()
|
.ml_neg_px()
|
||||||
.border_1()
|
.h(rems(0.875))
|
||||||
.border_color(cx.theme().colors().text_muted.alpha(0.12))
|
.items_center()
|
||||||
.child(div().flex_grow().child(self.left))
|
.overflow_hidden()
|
||||||
|
.px_0p5()
|
||||||
.child(
|
.child(
|
||||||
div()
|
Label::new(count.to_string())
|
||||||
.h_full()
|
.size(LabelSize::XSmall)
|
||||||
.w_px()
|
.line_height_style(LineHeightStyle::UiLabel),
|
||||||
.bg(cx.theme().colors().text_muted.alpha(0.16)),
|
|
||||||
)
|
)
|
||||||
.child(self.right)
|
|
||||||
.bg(ElevationIndex::Surface.on_elevation_bg(cx))
|
|
||||||
.shadow(smallvec::smallvec![BoxShadow {
|
|
||||||
color: hsla(0.0, 0.0, 0.0, 0.16),
|
|
||||||
offset: point(px(0.), px(1.)),
|
|
||||||
blur_radius: px(0.),
|
|
||||||
spread_radius: px(0.),
|
|
||||||
}])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let should_render_counts = left_icon.is_none() && (ahead_count > 0 || behind_count > 0);
|
||||||
|
|
||||||
|
let left = ui::ButtonLike::new_rounded_left(ElementId::Name(
|
||||||
|
format!("split-button-left-{}", id).into(),
|
||||||
|
))
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
|
.size(ui::ButtonSize::Compact)
|
||||||
|
.when(should_render_counts, |this| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.ml_neg_0p5()
|
||||||
|
.mr_1()
|
||||||
|
.when(behind_count > 0, |this| {
|
||||||
|
this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall))
|
||||||
|
.child(count(behind_count))
|
||||||
|
})
|
||||||
|
.when(ahead_count > 0, |this| {
|
||||||
|
this.child(Icon::new(IconName::ArrowUp).size(IconSize::XSmall))
|
||||||
|
.child(count(ahead_count))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(left_icon, |this, left_icon| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.ml_neg_0p5()
|
||||||
|
.mr_1()
|
||||||
|
.child(Icon::new(left_icon).size(IconSize::XSmall)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.child(Label::new(left_label).size(LabelSize::Small))
|
||||||
|
.mr_0p5(),
|
||||||
|
)
|
||||||
|
.on_click(left_on_click)
|
||||||
|
.tooltip(tooltip);
|
||||||
|
|
||||||
|
let right = render_git_action_menu(
|
||||||
|
ElementId::Name(format!("split-button-right-{}", id).into()),
|
||||||
|
keybinding_target,
|
||||||
|
)
|
||||||
|
.into_any_element();
|
||||||
|
|
||||||
|
SplitButton { left, right }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ mod button;
|
||||||
mod button_icon;
|
mod button_icon;
|
||||||
mod button_like;
|
mod button_like;
|
||||||
mod icon_button;
|
mod icon_button;
|
||||||
|
mod split_button;
|
||||||
mod toggle_button;
|
mod toggle_button;
|
||||||
|
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
pub use button_like::*;
|
pub use button_like::*;
|
||||||
pub use icon_button::*;
|
pub use icon_button::*;
|
||||||
|
pub use split_button::*;
|
||||||
pub use toggle_button::*;
|
pub use toggle_button::*;
|
||||||
|
|
45
crates/ui/src/components/button/split_button.rs
Normal file
45
crates/ui/src/components/button/split_button.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use gpui::{
|
||||||
|
div, hsla, point, px, AnyElement, App, BoxShadow, IntoElement, ParentElement, RenderOnce,
|
||||||
|
Styled, Window,
|
||||||
|
};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
|
use crate::{h_flex, ElevationIndex};
|
||||||
|
|
||||||
|
use super::ButtonLike;
|
||||||
|
|
||||||
|
/// /// A button with two parts: a primary action on the left and a secondary action on the right.
|
||||||
|
///
|
||||||
|
/// The left side is a [`ButtonLike`] with the main action, while the right side can contain
|
||||||
|
/// any element (typically a dropdown trigger or similar).
|
||||||
|
///
|
||||||
|
/// The two sections are visually separated by a divider, but presented as a unified control.
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct SplitButton {
|
||||||
|
pub left: ButtonLike,
|
||||||
|
pub right: AnyElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for SplitButton {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.rounded_sm()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().text_muted.alpha(0.12))
|
||||||
|
.child(div().flex_grow().child(self.left))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.h_full()
|
||||||
|
.w_px()
|
||||||
|
.bg(cx.theme().colors().text_muted.alpha(0.16)),
|
||||||
|
)
|
||||||
|
.child(self.right)
|
||||||
|
.bg(ElevationIndex::Surface.on_elevation_bg(cx))
|
||||||
|
.shadow(smallvec::smallvec![BoxShadow {
|
||||||
|
color: hsla(0.0, 0.0, 0.0, 0.16),
|
||||||
|
offset: point(px(0.), px(1.)),
|
||||||
|
blur_radius: px(0.),
|
||||||
|
spread_radius: px(0.),
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue