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:
Jason Lee 2025-02-25 15:32:59 +08:00 committed by GitHub
parent e5371c3209
commit c74791aac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 628 additions and 50 deletions

101
Cargo.lock generated
View file

@ -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",
]

View 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![
("Name", "GPUI Component", 1),
(
"Description",
"UI components for building fantastic desktop application by using [GPUI](https://gpui.rs).\
\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);
});
}

View 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);
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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,
}

View file

@ -34,7 +34,7 @@ use super::{
#[derive(Clone)]
pub(super) struct MarkdownElement {
id: ElementId,
text: SharedString,
pub(super) text: SharedString,
style: TextViewStyle,
}

View file

@ -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