Skip to repository content107 lines · 3.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:34:14.686Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
split_button.rs
1use gpui::{
2 AnyElement, App, BoxShadow, ParentElement, RenderOnce, Styled, Window, div, hsla, prelude::*,
3 px,
4};
5
6use theme::ActiveTheme;
7
8use crate::{Divider, ElevationIndex, prelude::*};
9
10use super::ButtonLike;
11
12#[derive(Clone, Copy, PartialEq)]
13pub enum SplitButtonStyle {
14 Filled,
15 Outlined,
16 Transparent,
17}
18
19pub enum SplitButtonKind {
20 ButtonLike(ButtonLike),
21 IconButton(IconButton),
22}
23
24impl From<IconButton> for SplitButtonKind {
25 fn from(icon_button: IconButton) -> Self {
26 Self::IconButton(icon_button)
27 }
28}
29
30impl From<ButtonLike> for SplitButtonKind {
31 fn from(button_like: ButtonLike) -> Self {
32 Self::ButtonLike(button_like)
33 }
34}
35
36/// /// A button with two parts: a primary action on the left and a secondary action on the right.
37///
38/// The left side is a [`ButtonLike`] with the main action, while the right side can contain
39/// any element (typically a dropdown trigger or similar).
40///
41/// The two sections are visually separated by a divider, but presented as a unified control.
42#[derive(IntoElement)]
43pub struct SplitButton {
44 left: SplitButtonKind,
45 right: AnyElement,
46 style: SplitButtonStyle,
47}
48
49impl SplitButton {
50 pub fn new(left: impl Into<SplitButtonKind>, right: AnyElement) -> Self {
51 Self {
52 left: left.into(),
53 right,
54 style: SplitButtonStyle::Filled,
55 }
56 }
57
58 pub fn style(mut self, style: SplitButtonStyle) -> Self {
59 self.style = style;
60 self
61 }
62}
63
64impl RenderOnce for SplitButton {
65 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
66 let is_filled_or_outlined = matches!(
67 self.style,
68 SplitButtonStyle::Filled | SplitButtonStyle::Outlined
69 );
70
71 let outline = BoxShadow::new(px(0.), px(0.), cx.theme().colors().border.opacity(0.8))
72 .spread_radius(px(1.))
73 .inset();
74
75 h_flex()
76 .when(is_filled_or_outlined, |this| this.relative().rounded_sm())
77 .when(self.style == SplitButtonStyle::Transparent, |this| {
78 this.gap_px()
79 })
80 .child(div().flex_grow_1().child(match self.left {
81 SplitButtonKind::ButtonLike(button) => button.into_any_element(),
82 SplitButtonKind::IconButton(icon) => icon.into_any_element(),
83 }))
84 .child(Divider::vertical().when(is_filled_or_outlined, |s| {
85 s.h_full().color(crate::DividerColor::Border)
86 }))
87 .child(self.right)
88 .when(is_filled_or_outlined, |this| {
89 this.child(
90 div()
91 .absolute()
92 .inset_0()
93 .rounded_sm()
94 .shadow(vec![outline]),
95 )
96 })
97 .when(self.style == SplitButtonStyle::Filled, |this| {
98 this.bg(ElevationIndex::Surface.on_elevation_bg(cx))
99 .shadow(vec![BoxShadow::new(
100 px(0.),
101 px(1.),
102 hsla(0.0, 0.0, 0.0, 0.16),
103 )])
104 })
105 }
106}
107