description_list: Add DescriptionList. (#658)
<img width="1089" alt="image" src="https://github.com/user-attachments/assets/15069422-14aa-4fa3-b661-65fcf7417323" /> <img width="1089" alt="image" src="https://github.com/user-attachments/assets/df4a4b90-b304-40d3-a9ce-1be73eaaa9f4" /> <img width="1089" alt="image" src="https://github.com/user-attachments/assets/1deaf1e8-4b30-4afd-90af-1bd0eecf53e2" />
This commit is contained in:
parent
e5371c3209
commit
c74791aac8
8 changed files with 628 additions and 50 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
|
@ -181,7 +181,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_repr",
|
||||
"url",
|
||||
"zbus 5.1.1",
|
||||
"zbus 5.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -228,7 +228,7 @@ dependencies = [
|
|||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand 2.1.1",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
"slab",
|
||||
]
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
|
|||
dependencies = [
|
||||
"async-lock 3.4.0",
|
||||
"blocking",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -277,15 +277,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.3.4"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
|
||||
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
|
||||
dependencies = [
|
||||
"async-lock 3.4.0",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
"parking",
|
||||
"polling 3.7.3",
|
||||
"rustix 0.38.38",
|
||||
|
|
@ -331,9 +331,9 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
|
||||
dependencies = [
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"blocking",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -360,14 +360,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
||||
dependencies = [
|
||||
"async-channel 2.3.1",
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"async-lock 3.4.0",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener 5.3.1",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
"rustix 0.38.38",
|
||||
"tracing",
|
||||
]
|
||||
|
|
@ -389,7 +389,7 @@ version = "0.2.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
|
||||
dependencies = [
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"async-lock 3.4.0",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
|
|
@ -527,7 +527,7 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
|
|
@ -684,7 +684,7 @@ dependencies = [
|
|||
"async-channel 2.3.1",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
"piper",
|
||||
]
|
||||
|
||||
|
|
@ -1381,9 +1381,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
|
|
@ -2082,9 +2082,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.4.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210"
|
||||
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
|
||||
dependencies = [
|
||||
"fastrand 2.1.1",
|
||||
"futures-core",
|
||||
|
|
@ -3915,7 +3915,7 @@ version = "0.7.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
|
|
@ -4170,7 +4170,7 @@ source = "git+https://github.com/zed-industries/oo7?branch=avoid-crypto-panic#9d
|
|||
dependencies = [
|
||||
"aes",
|
||||
"async-fs 2.1.2",
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"async-lock 3.4.0",
|
||||
"async-net 2.0.0",
|
||||
"blocking",
|
||||
|
|
@ -4178,7 +4178,7 @@ dependencies = [
|
|||
"cipher",
|
||||
"digest",
|
||||
"endi",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-util",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
|
|
@ -5893,12 +5893,12 @@ dependencies = [
|
|||
"async-channel 2.3.1",
|
||||
"async-executor",
|
||||
"async-fs 2.1.2",
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"async-lock 3.4.0",
|
||||
"async-net 2.0.0",
|
||||
"async-process 2.3.0",
|
||||
"blocking",
|
||||
"futures-lite 2.4.0",
|
||||
"futures-lite 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7646,6 +7646,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
|
|
@ -7872,7 +7881,7 @@ dependencies = [
|
|||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-fs 2.1.2",
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"async-lock 3.4.0",
|
||||
"async-process 2.3.0",
|
||||
"async-recursion",
|
||||
|
|
@ -7903,14 +7912,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.1.1"
|
||||
version = "5.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608"
|
||||
checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-fs 2.1.2",
|
||||
"async-io 2.3.4",
|
||||
"async-io 2.4.0",
|
||||
"async-lock 3.4.0",
|
||||
"async-process 2.3.0",
|
||||
"async-recursion",
|
||||
|
|
@ -7920,7 +7929,7 @@ dependencies = [
|
|||
"enumflags2",
|
||||
"event-listener 5.3.1",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"futures-lite 2.6.0",
|
||||
"hex",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
|
|
@ -7930,11 +7939,11 @@ dependencies = [
|
|||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.59.0",
|
||||
"winnow 0.6.20",
|
||||
"winnow 0.7.3",
|
||||
"xdg-home",
|
||||
"zbus_macros 5.1.1",
|
||||
"zbus_macros 5.5.0",
|
||||
"zbus_names 4.1.0",
|
||||
"zvariant 5.1.0",
|
||||
"zvariant 5.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7952,17 +7961,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.1.1"
|
||||
version = "5.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d"
|
||||
checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
"zbus_names 4.1.0",
|
||||
"zvariant 5.1.0",
|
||||
"zvariant_utils 3.0.2",
|
||||
"zvariant 5.4.0",
|
||||
"zvariant_utils 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7985,7 +7994,7 @@ dependencies = [
|
|||
"serde",
|
||||
"static_assertions",
|
||||
"winnow 0.6.20",
|
||||
"zvariant 5.1.0",
|
||||
"zvariant 5.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -8074,18 +8083,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.1.0"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f"
|
||||
checksum = "b2df9ee044893fcffbdc25de30546edef3e32341466811ca18421e3cd6c5a3ac"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"url",
|
||||
"winnow 0.6.20",
|
||||
"zvariant_derive 5.1.0",
|
||||
"zvariant_utils 3.0.2",
|
||||
"winnow 0.7.3",
|
||||
"zvariant_derive 5.4.0",
|
||||
"zvariant_utils 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -8103,15 +8112,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.1.0"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916"
|
||||
checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
"zvariant_utils 3.0.2",
|
||||
"zvariant_utils 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -8127,14 +8136,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "3.0.2"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6"
|
||||
checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"syn 2.0.96",
|
||||
"winnow 0.6.20",
|
||||
"winnow 0.7.3",
|
||||
]
|
||||
|
|
|
|||
177
crates/story/examples/description_list.rs
Normal file
177
crates/story/examples/description_list.rs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
use gpui::*;
|
||||
use gpui_component::{
|
||||
button::Button,
|
||||
checkbox::Checkbox,
|
||||
description_list::{DescriptionItem, DescriptionList},
|
||||
h_flex,
|
||||
popup_menu::PopupMenuExt as _,
|
||||
text::TextView,
|
||||
v_flex, AxisExt, Sizable as _, Size,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use story::Assets;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||
struct ChangeSize(Size);
|
||||
|
||||
impl_internal_actions!(example, [ChangeSize]);
|
||||
|
||||
pub struct Example {
|
||||
layout: Axis,
|
||||
bordered: bool,
|
||||
size: Size,
|
||||
items: Vec<(&'static str, &'static str, usize)>,
|
||||
}
|
||||
|
||||
impl Example {
|
||||
pub fn new(_: &mut Window, _: &mut Context<Self>) -> Self {
|
||||
let items = vec.\
|
||||
\n\n \
|
||||
Contains a lot of useful UI components, such as **Button**, **TextInput**, **Table**, **List**, **Dropdown**, **DatePicker** ... \
|
||||
\n\n \
|
||||
You can easily create your native desktop application by using GPUI Component.
|
||||
",
|
||||
3,
|
||||
),
|
||||
("Version", "0.1.0", 1),
|
||||
("License", "Apache-2.0", 1),
|
||||
("Author", "Longbridge", 1),
|
||||
("--", "--", 1),
|
||||
(
|
||||
"Repository",
|
||||
"https://github.com/longbridge/gpui-component",
|
||||
2,
|
||||
),
|
||||
(
|
||||
"Category",
|
||||
"UI, Desktop, Framework",
|
||||
1,
|
||||
),
|
||||
(
|
||||
"Platform",
|
||||
"macOS, Windows, Linux",
|
||||
1,
|
||||
),
|
||||
];
|
||||
|
||||
Self {
|
||||
items,
|
||||
bordered: true,
|
||||
size: Size::default(),
|
||||
layout: Axis::Horizontal,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self::new(window, cx))
|
||||
}
|
||||
|
||||
fn set_layout(&mut self, layout: Axis, cx: &mut Context<Self>) {
|
||||
self.layout = layout;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_bordered(&mut self, bordered: bool, cx: &mut Context<Self>) {
|
||||
self.bordered = bordered;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_change_size(&mut self, a: &ChangeSize, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.size = a.0;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Example {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("example")
|
||||
.on_action(cx.listener(Self::on_change_size))
|
||||
.p_4()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.child(
|
||||
Checkbox::new("layout")
|
||||
.checked(self.layout.is_vertical())
|
||||
.label("Vertical Layout")
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
let new_layout = if *checked {
|
||||
Axis::Vertical
|
||||
} else {
|
||||
Axis::Horizontal
|
||||
};
|
||||
this.set_layout(new_layout, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Checkbox::new("bordered")
|
||||
.checked(self.bordered)
|
||||
.label("Bordered")
|
||||
.on_click(cx.listener(|this, checked: &bool, _, cx| {
|
||||
this.set_bordered(*checked, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("size")
|
||||
.small()
|
||||
.label(format!("size: {:?}", self.size))
|
||||
.popup_menu({
|
||||
let size = self.size;
|
||||
move |menu, _, _| {
|
||||
menu.menu_with_check(
|
||||
"Large",
|
||||
size == Size::Large,
|
||||
Box::new(ChangeSize(Size::Large)),
|
||||
)
|
||||
.menu_with_check(
|
||||
"Medium",
|
||||
size == Size::Medium,
|
||||
Box::new(ChangeSize(Size::Medium)),
|
||||
)
|
||||
.menu_with_check(
|
||||
"Small",
|
||||
size == Size::Small,
|
||||
Box::new(ChangeSize(Size::Small)),
|
||||
)
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
DescriptionList::new()
|
||||
.columns(3)
|
||||
.layout(self.layout)
|
||||
.bordered(self.bordered)
|
||||
.with_size(self.size)
|
||||
.children(self.items.clone().into_iter().enumerate().map(
|
||||
|(ix, (label, value, span))| {
|
||||
if label == "--" {
|
||||
return DescriptionItem::Divider;
|
||||
}
|
||||
|
||||
DescriptionItem::new(label)
|
||||
.value(TextView::markdown(ix, value).into_any_element())
|
||||
.span(span)
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = Application::new().with_assets(Assets);
|
||||
|
||||
app.run(move |cx| {
|
||||
story::init(cx);
|
||||
cx.activate(true);
|
||||
|
||||
story::create_new_window("Description List Example", Example::view, cx);
|
||||
});
|
||||
}
|
||||
368
crates/ui/src/description_list.rs
Normal file
368
crates/ui/src/description_list.rs
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, AnyElement, App, Axis, DefiniteLength, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, Styled, Window,
|
||||
};
|
||||
|
||||
use crate::{h_flex, text::Text, v_flex, ActiveTheme as _, AxisExt, Sizable, Size};
|
||||
|
||||
/// A description list.
|
||||
#[derive(IntoElement)]
|
||||
pub struct DescriptionList {
|
||||
items: Vec<DescriptionItem>,
|
||||
size: Size,
|
||||
layout: Axis,
|
||||
label_width: DefiniteLength,
|
||||
bordered: bool,
|
||||
columns: usize,
|
||||
}
|
||||
|
||||
/// Description item.
|
||||
pub enum DescriptionItem {
|
||||
Item {
|
||||
label: DescriptionText,
|
||||
value: DescriptionText,
|
||||
span: usize,
|
||||
},
|
||||
Divider,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub enum DescriptionText {
|
||||
String(SharedString),
|
||||
Text(Text),
|
||||
AnyElement(AnyElement),
|
||||
}
|
||||
|
||||
impl From<&str> for DescriptionText {
|
||||
fn from(text: &str) -> Self {
|
||||
DescriptionText::String(SharedString::from(text.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Text> for DescriptionText {
|
||||
fn from(text: Text) -> Self {
|
||||
DescriptionText::Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnyElement> for DescriptionText {
|
||||
fn from(element: AnyElement) -> Self {
|
||||
DescriptionText::AnyElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedString> for DescriptionText {
|
||||
fn from(text: SharedString) -> Self {
|
||||
DescriptionText::String(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for DescriptionText {
|
||||
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||
match self {
|
||||
DescriptionText::String(text) => div().child(text).into_any_element(),
|
||||
DescriptionText::Text(text) => text.into_any_element(),
|
||||
DescriptionText::AnyElement(element) => element,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DescriptionItem {
|
||||
/// Create a new description item, with a label.
|
||||
///
|
||||
/// The value is an empty element.
|
||||
pub fn new(label: impl Into<DescriptionText>) -> Self {
|
||||
DescriptionItem::Item {
|
||||
label: label.into(),
|
||||
value: "".into(),
|
||||
span: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the element value of the item.
|
||||
pub fn value(mut self, value: impl Into<DescriptionText>) -> Self {
|
||||
let new_value = value.into();
|
||||
if let DescriptionItem::Item { value, .. } = &mut self {
|
||||
*value = new_value;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the span of the item.
|
||||
///
|
||||
/// This method only works for [`DescriptionItem::Item`].
|
||||
pub fn span(mut self, span: usize) -> Self {
|
||||
let val = span;
|
||||
if let DescriptionItem::Item { span, .. } = &mut self {
|
||||
*span = val;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn _label(&self) -> Option<&DescriptionText> {
|
||||
match self {
|
||||
DescriptionItem::Item { label, .. } => Some(label),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn _span(&self) -> Option<usize> {
|
||||
match self {
|
||||
DescriptionItem::Item { span, .. } => Some(*span),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DescriptionList {
|
||||
/// Create a new description list with the default layout (Horizontal).
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: Vec::new(),
|
||||
layout: Axis::Horizontal,
|
||||
label_width: px(120.).into(),
|
||||
size: Size::default(),
|
||||
bordered: true,
|
||||
columns: 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a vertical description list.
|
||||
pub fn vertical() -> Self {
|
||||
Self::new().layout(Axis::Vertical)
|
||||
}
|
||||
|
||||
/// Create a horizontal description list, the default.
|
||||
pub fn horizontal() -> Self {
|
||||
Self::new().layout(Axis::Horizontal)
|
||||
}
|
||||
|
||||
/// Set the width of the label, only works for horizontal layout.
|
||||
///
|
||||
/// Default is `120px`.
|
||||
pub fn label_width(mut self, label_width: DefiniteLength) -> Self {
|
||||
self.label_width = label_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the layout of the description list.
|
||||
pub fn layout(mut self, layout: Axis) -> Self {
|
||||
self.layout = layout;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the border of the description list, default is `true`.
|
||||
///
|
||||
/// `Horizontal` layout only.
|
||||
pub fn bordered(mut self, bordered: bool) -> Self {
|
||||
self.bordered = bordered;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the number of columns in the description list, default is `3`.
|
||||
///
|
||||
/// A value between `1` and `10` is allowed.
|
||||
pub fn columns(mut self, columns: usize) -> Self {
|
||||
self.columns = columns.clamp(1, 10);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a [`DescriptionItem::Item`] to the list.
|
||||
pub fn child(
|
||||
mut self,
|
||||
label: impl Into<DescriptionText>,
|
||||
value: impl Into<DescriptionText>,
|
||||
span: usize,
|
||||
) -> Self {
|
||||
self.items.push(DescriptionItem::Item {
|
||||
label: label.into(),
|
||||
value: value.into(),
|
||||
span,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Add children to the list.
|
||||
pub fn children(
|
||||
mut self,
|
||||
children: impl IntoIterator<Item = impl Into<DescriptionItem>>,
|
||||
) -> Self {
|
||||
self.items
|
||||
.extend(children.into_iter().map(Into::into).collect::<Vec<_>>());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a divider to the list.
|
||||
pub fn divider(mut self) -> Self {
|
||||
self.items.push(DescriptionItem::Divider);
|
||||
self
|
||||
}
|
||||
|
||||
fn group_item_rows(items: Vec<DescriptionItem>, columns: usize) -> Vec<Vec<DescriptionItem>> {
|
||||
let mut rows = vec![];
|
||||
let mut current_span = 0;
|
||||
for item in items.into_iter() {
|
||||
let span = item._span().unwrap_or(columns);
|
||||
if rows.is_empty() {
|
||||
rows.push(vec![]);
|
||||
}
|
||||
if current_span + span > columns {
|
||||
rows.push(vec![]);
|
||||
current_span = 0;
|
||||
}
|
||||
let last_group = rows.last_mut().unwrap();
|
||||
last_group.push(item);
|
||||
current_span += span;
|
||||
}
|
||||
// Remove last empty rows if it exists
|
||||
while let Some(last_group) = rows.last() {
|
||||
if !last_group.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
rows.pop();
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
}
|
||||
|
||||
impl Sizable for DescriptionList {
|
||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||
self.size = size.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for DescriptionList {
|
||||
fn render(self, _: &mut Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
||||
let base_gap = match self.size {
|
||||
Size::XSmall | Size::Small => px(2.),
|
||||
Size::Medium => px(4.),
|
||||
Size::Large => px(8.),
|
||||
_ => px(4.),
|
||||
};
|
||||
|
||||
let gap = if self.layout.is_vertical() {
|
||||
base_gap
|
||||
} else {
|
||||
px(0.)
|
||||
};
|
||||
// Only for Horizontal layout
|
||||
let (padding_x, padding_y) = match self.size {
|
||||
Size::XSmall | Size::Small => (px(4.), px(2.)),
|
||||
Size::Medium => (px(8.), px(4.)),
|
||||
Size::Large => (px(12.), px(6.)),
|
||||
_ => (px(8.), px(4.)),
|
||||
};
|
||||
|
||||
let label_width = if self.layout.is_horizontal() {
|
||||
Some(self.label_width)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let bordered = self.bordered && self.layout.is_horizontal();
|
||||
|
||||
// Group items by columns
|
||||
let rows = Self::group_item_rows(self.items, self.columns);
|
||||
let rows_len = rows.len();
|
||||
|
||||
v_flex()
|
||||
.gap(gap)
|
||||
.overflow_hidden()
|
||||
.when(bordered, |this| {
|
||||
this.rounded(padding_x)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
})
|
||||
.children(rows.into_iter().enumerate().map(|(ix, items)| {
|
||||
let is_last = ix == rows_len - 1;
|
||||
h_flex()
|
||||
.when(bordered && !is_last, |this| {
|
||||
this.border_b_1().border_color(cx.theme().border)
|
||||
})
|
||||
.children({
|
||||
items.into_iter().enumerate().map(|(item_ix, item)| {
|
||||
let is_first_col = item_ix == 0;
|
||||
|
||||
match item {
|
||||
DescriptionItem::Item { label, value, .. } => {
|
||||
let el = if self.layout.is_vertical() {
|
||||
v_flex()
|
||||
} else {
|
||||
h_flex().h_full()
|
||||
};
|
||||
|
||||
el.flex_1()
|
||||
.child(
|
||||
div()
|
||||
.when(self.layout.is_horizontal(), |this| {
|
||||
this.h_full()
|
||||
})
|
||||
.text_color(
|
||||
cx.theme().description_list_label_foreground,
|
||||
)
|
||||
.text_sm()
|
||||
.map(|this| match label_width {
|
||||
Some(label_width) => this
|
||||
.w(label_width)
|
||||
.px(padding_x)
|
||||
.py(padding_y)
|
||||
.flex_shrink_0()
|
||||
.when(bordered, |this| {
|
||||
this.border_r_1()
|
||||
.when(!is_first_col, |this| {
|
||||
this.border_l_1()
|
||||
})
|
||||
.border_color(cx.theme().border)
|
||||
.bg(cx
|
||||
.theme()
|
||||
.description_list_label)
|
||||
}),
|
||||
None => this,
|
||||
})
|
||||
.child(label),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.when(self.layout.is_horizontal(), |this| {
|
||||
this.px(padding_x).py(padding_y)
|
||||
})
|
||||
.overflow_hidden()
|
||||
.child(value),
|
||||
)
|
||||
}
|
||||
_ => div().h_2().w_full().when(bordered, |this| {
|
||||
this.bg(cx.theme().description_list_label)
|
||||
}),
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::DescriptionItem;
|
||||
|
||||
#[test]
|
||||
fn test_group_item_rows() {
|
||||
let items = vec![
|
||||
DescriptionItem::new("test1"),
|
||||
DescriptionItem::new("test2").span(2),
|
||||
DescriptionItem::new("test3"),
|
||||
DescriptionItem::new("test4"),
|
||||
DescriptionItem::new("test5"),
|
||||
DescriptionItem::new("test6").span(3),
|
||||
DescriptionItem::new("test7"),
|
||||
];
|
||||
let rows = super::DescriptionList::group_item_rows(items, 3);
|
||||
assert_eq!(rows.len(), 4);
|
||||
assert_eq!(rows[0].len(), 2);
|
||||
assert_eq!(rows[1].len(), 3);
|
||||
assert_eq!(rows[2].len(), 1);
|
||||
assert_eq!(rows[3].len(), 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ pub mod checkbox;
|
|||
pub mod clipboard;
|
||||
pub mod color_picker;
|
||||
pub mod context_menu;
|
||||
pub mod description_list;
|
||||
pub mod divider;
|
||||
pub mod dock;
|
||||
pub mod drawer;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ use crate::{
|
|||
ActiveTheme,
|
||||
};
|
||||
use gpui::{
|
||||
div, px, App, Axis, Div, Edges, Element, ElementId, EntityId, FocusHandle, Pixels, Styled,
|
||||
Window,
|
||||
div, px, App, Axis, DefiniteLength, Div, Edges, Element, ElementId, EntityId, FocusHandle,
|
||||
Pixels, Styled, Window,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -46,6 +46,18 @@ pub trait StyledExt: Styled + Sized {
|
|||
self.flex().flex_col()
|
||||
}
|
||||
|
||||
/// Apply paddings to the element.
|
||||
fn paddings<L>(self, paddings: impl Into<Edges<L>>) -> Self
|
||||
where
|
||||
L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug,
|
||||
{
|
||||
let paddings = paddings.into();
|
||||
self.pt(paddings.top.into())
|
||||
.pb(paddings.bottom.into())
|
||||
.pl(paddings.left.into())
|
||||
.pr(paddings.right.into())
|
||||
}
|
||||
|
||||
/// Render a border with a width of 1px, color red
|
||||
fn debug_red(self) -> Self {
|
||||
if cfg!(debug_assertions) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ pub(super) fn parse_html(source: &str) -> Result<element::Node, SharedString> {
|
|||
#[derive(Clone)]
|
||||
pub(super) struct HtmlElement {
|
||||
id: ElementId,
|
||||
text: SharedString,
|
||||
pub(super) text: SharedString,
|
||||
style: TextViewStyle,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ use super::{
|
|||
#[derive(Clone)]
|
||||
pub(super) struct MarkdownElement {
|
||||
id: ElementId,
|
||||
text: SharedString,
|
||||
pub(super) text: SharedString,
|
||||
style: TextViewStyle,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ pub struct ThemeColor {
|
|||
pub danger_foreground: Hsla,
|
||||
/// Danger hover background color.
|
||||
pub danger_hover: Hsla,
|
||||
/// Description List label background color.
|
||||
pub description_list_label: Hsla,
|
||||
/// Description List label foreground color.
|
||||
pub description_list_label_foreground: Hsla,
|
||||
/// Drag border color.
|
||||
pub drag_border: Hsla,
|
||||
/// Drop target background color.
|
||||
|
|
@ -224,6 +228,8 @@ impl ThemeColor {
|
|||
danger_active: hsl(0.0, 84.2, 47.0),
|
||||
danger_foreground: hsl(0.0, 0.0, 98.0),
|
||||
danger_hover: hsl(0.0, 84.2, 65.0),
|
||||
description_list_label: hsl(240.0, 5.9, 96.9),
|
||||
description_list_label_foreground: hsl(240.0, 5.9, 10.0),
|
||||
drag_border: crate::blue_500(),
|
||||
drop_target: hsl(235.0, 30., 44.0).opacity(0.25),
|
||||
foreground: hsl(240.0, 10., 3.9),
|
||||
|
|
@ -301,6 +307,8 @@ impl ThemeColor {
|
|||
danger_active: hsl(0.0, 62.8, 20.6),
|
||||
danger_foreground: hsl(0.0, 0.0, 78.0),
|
||||
danger_hover: hsl(0.0, 62.8, 35.6),
|
||||
description_list_label: hsl(240.0, 0., 13.0),
|
||||
description_list_label_foreground: hsl(0.0, 0.0, 78.0),
|
||||
drag_border: crate::blue_500(),
|
||||
drop_target: hsl(235.0, 30., 44.0).opacity(0.1),
|
||||
foreground: hsl(0., 0., 78.),
|
||||
|
|
@ -483,6 +491,9 @@ impl Theme {
|
|||
self.sidebar_primary = self.sidebar_primary.apply(mask_color);
|
||||
self.sidebar_primary_foreground = self.sidebar_primary_foreground.apply(mask_color);
|
||||
self.tiles = self.tiles.apply(mask_color);
|
||||
self.description_list_label = self.description_list_label.apply(mask_color);
|
||||
self.description_list_label_foreground =
|
||||
self.description_list_label_foreground.apply(mask_color);
|
||||
}
|
||||
|
||||
/// Sync the theme with the system appearance
|
||||
|
|
|
|||
Loading…
Reference in a new issue