Move UI out of storybook2 and into ui2
This commit is contained in:
@@ -0,0 +1,519 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use gpui3::{div, Div, Hsla, WindowContext};
|
||||
|
||||
use crate::theme::theme;
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
h_stack, token, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label, LabelColor,
|
||||
LabelSize,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq)]
|
||||
pub enum ListItemVariant {
|
||||
/// The list item extends to the far left and right of the list.
|
||||
#[default]
|
||||
FullWidth,
|
||||
Inset,
|
||||
}
|
||||
|
||||
#[derive(Element, Clone)]
|
||||
pub struct ListHeader<S: 'static + Send + Sync + Clone> {
|
||||
state_type: PhantomData<S>,
|
||||
label: &'static str,
|
||||
left_icon: Option<Icon>,
|
||||
variant: ListItemVariant,
|
||||
state: InteractionState,
|
||||
toggleable: Toggleable,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> ListHeader<S> {
|
||||
pub fn new(label: &'static str) -> Self {
|
||||
Self {
|
||||
state_type: PhantomData,
|
||||
label,
|
||||
left_icon: None,
|
||||
variant: ListItemVariant::default(),
|
||||
state: InteractionState::default(),
|
||||
toggleable: Toggleable::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
||||
self.toggleable = toggle.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self {
|
||||
self.toggleable = toggleable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||
self.left_icon = left_icon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
self
|
||||
}
|
||||
|
||||
fn disclosure_control(&self) -> Div<S> {
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||
|
||||
match (is_toggleable, is_toggled) {
|
||||
(false, _) => div(),
|
||||
(_, true) => div().child(IconElement::new(Icon::ChevronRight).color(IconColor::Muted)),
|
||||
(_, false) => div().child(IconElement::new(Icon::ChevronDown).size(IconSize::Small)),
|
||||
}
|
||||
}
|
||||
|
||||
fn background_color(&self, cx: &WindowContext) -> Hsla {
|
||||
let theme = theme(cx);
|
||||
let system_color = SystemColor::new();
|
||||
|
||||
match self.state {
|
||||
InteractionState::Hovered => theme.lowest.base.hovered.background,
|
||||
InteractionState::Active => theme.lowest.base.pressed.background,
|
||||
InteractionState::Enabled => theme.lowest.on.default.background,
|
||||
_ => system_color.transparent,
|
||||
}
|
||||
}
|
||||
|
||||
fn label_color(&self) -> LabelColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => LabelColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> IconColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => IconColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
let token = token();
|
||||
let system_color = SystemColor::new();
|
||||
let background_color = self.background_color(cx);
|
||||
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||
|
||||
let disclosure_control = self.disclosure_control();
|
||||
|
||||
h_stack()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.fill(background_color)
|
||||
.when(self.state == InteractionState::Focused, |this| {
|
||||
this.border()
|
||||
.border_color(theme.lowest.accent.default.border)
|
||||
})
|
||||
.relative()
|
||||
.py_1()
|
||||
.child(
|
||||
div()
|
||||
.h_6()
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
.flex()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.children(self.left_icon.map(|i| {
|
||||
IconElement::new(i)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small)
|
||||
}))
|
||||
.child(
|
||||
Label::new(self.label.clone())
|
||||
.color(LabelColor::Muted)
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
.child(disclosure_control),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element, Clone)]
|
||||
pub struct ListSubHeader<S: 'static + Send + Sync + Clone> {
|
||||
state_type: PhantomData<S>,
|
||||
label: &'static str,
|
||||
left_icon: Option<Icon>,
|
||||
variant: ListItemVariant,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> ListSubHeader<S> {
|
||||
pub fn new(label: &'static str) -> Self {
|
||||
Self {
|
||||
state_type: PhantomData,
|
||||
label,
|
||||
left_icon: None,
|
||||
variant: ListItemVariant::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||
self.left_icon = left_icon;
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
let token = token();
|
||||
|
||||
h_stack().flex_1().w_full().relative().py_1().child(
|
||||
div()
|
||||
.h_6()
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
.flex()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.children(self.left_icon.map(|i| {
|
||||
IconElement::new(i)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small)
|
||||
}))
|
||||
.child(
|
||||
Label::new(self.label.clone())
|
||||
.color(LabelColor::Muted)
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LeftContent {
|
||||
Icon(Icon),
|
||||
Avatar(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum ListEntrySize {
|
||||
#[default]
|
||||
Small,
|
||||
Medium,
|
||||
}
|
||||
|
||||
#[derive(Clone, Element)]
|
||||
pub enum ListItem<S: 'static + Send + Sync + Clone> {
|
||||
Entry(ListEntry<S>),
|
||||
Separator(ListSeparator<S>),
|
||||
Header(ListSubHeader<S>),
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> From<ListEntry<S>> for ListItem<S> {
|
||||
fn from(entry: ListEntry<S>) -> Self {
|
||||
Self::Entry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> From<ListSeparator<S>> for ListItem<S> {
|
||||
fn from(entry: ListSeparator<S>) -> Self {
|
||||
Self::Separator(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> From<ListSubHeader<S>> for ListItem<S> {
|
||||
fn from(entry: ListSubHeader<S>) -> Self {
|
||||
Self::Header(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> ListItem<S> {
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
match self {
|
||||
ListItem::Entry(entry) => div().child(entry.render(cx)),
|
||||
ListItem::Separator(separator) => div().child(separator.render(cx)),
|
||||
ListItem::Header(header) => div().child(header.render(cx)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(label: Label<S>) -> Self {
|
||||
Self::Entry(ListEntry::new(label))
|
||||
}
|
||||
|
||||
pub fn as_entry(&mut self) -> Option<&mut ListEntry<S>> {
|
||||
if let Self::Entry(entry) = self {
|
||||
Some(entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element, Clone)]
|
||||
pub struct ListEntry<S: 'static + Send + Sync + Clone> {
|
||||
disclosure_control_style: DisclosureControlVisibility,
|
||||
indent_level: u32,
|
||||
label: Label<S>,
|
||||
left_content: Option<LeftContent>,
|
||||
variant: ListItemVariant,
|
||||
size: ListEntrySize,
|
||||
state: InteractionState,
|
||||
toggle: Option<ToggleState>,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
|
||||
pub fn new(label: Label<S>) -> Self {
|
||||
Self {
|
||||
disclosure_control_style: DisclosureControlVisibility::default(),
|
||||
indent_level: 0,
|
||||
label,
|
||||
variant: ListItemVariant::default(),
|
||||
left_content: None,
|
||||
size: ListEntrySize::default(),
|
||||
state: InteractionState::default(),
|
||||
toggle: None,
|
||||
}
|
||||
}
|
||||
pub fn variant(mut self, variant: ListItemVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
pub fn indent_level(mut self, indent_level: u32) -> Self {
|
||||
self.indent_level = indent_level;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
||||
self.toggle = Some(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_content(mut self, left_content: LeftContent) -> Self {
|
||||
self.left_content = Some(left_content);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Icon) -> Self {
|
||||
self.left_content = Some(LeftContent::Icon(left_icon));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_avatar(mut self, left_avatar: &'static str) -> Self {
|
||||
self.left_content = Some(LeftContent::Avatar(left_avatar));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: ListEntrySize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disclosure_control_style(
|
||||
mut self,
|
||||
disclosure_control_style: DisclosureControlVisibility,
|
||||
) -> Self {
|
||||
self.disclosure_control_style = disclosure_control_style;
|
||||
self
|
||||
}
|
||||
|
||||
fn background_color(&self, cx: &WindowContext) -> Hsla {
|
||||
let theme = theme(cx);
|
||||
let system_color = SystemColor::new();
|
||||
|
||||
match self.state {
|
||||
InteractionState::Hovered => theme.lowest.base.hovered.background,
|
||||
InteractionState::Active => theme.lowest.base.pressed.background,
|
||||
InteractionState::Enabled => theme.lowest.on.default.background,
|
||||
_ => system_color.transparent,
|
||||
}
|
||||
}
|
||||
|
||||
fn label_color(&self) -> LabelColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => LabelColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> IconColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => IconColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn disclosure_control(&mut self, cx: &mut ViewContext<S>) -> Option<impl Element<State = S>> {
|
||||
let theme = theme(cx);
|
||||
let token = token();
|
||||
|
||||
let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
|
||||
IconElement::new(Icon::ChevronDown)
|
||||
} else {
|
||||
IconElement::new(Icon::ChevronRight)
|
||||
}
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small);
|
||||
|
||||
match (self.toggle, self.disclosure_control_style) {
|
||||
(Some(_), DisclosureControlVisibility::OnHover) => {
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
// .neg_left_5()
|
||||
.child(disclosure_control_icon),
|
||||
)
|
||||
}
|
||||
(Some(_), DisclosureControlVisibility::Always) => {
|
||||
Some(div().child(disclosure_control_icon))
|
||||
}
|
||||
(None, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
let token = token();
|
||||
let system_color = SystemColor::new();
|
||||
let background_color = self.background_color(cx);
|
||||
|
||||
let left_content = match self.left_content {
|
||||
Some(LeftContent::Icon(i)) => {
|
||||
Some(h_stack().child(IconElement::new(i).size(IconSize::Small)))
|
||||
}
|
||||
Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let sized_item = match self.size {
|
||||
ListEntrySize::Small => div().h_6(),
|
||||
ListEntrySize::Medium => div().h_7(),
|
||||
};
|
||||
|
||||
div()
|
||||
.fill(background_color)
|
||||
.when(self.state == InteractionState::Focused, |this| {
|
||||
this.border()
|
||||
.border_color(theme.lowest.accent.default.border)
|
||||
})
|
||||
.relative()
|
||||
.py_1()
|
||||
.child(
|
||||
sized_item
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
// .ml(rems(0.75 * self.indent_level as f32))
|
||||
.children((0..self.indent_level).map(|_| {
|
||||
div()
|
||||
// .w(token.list_indent_depth)
|
||||
.h_full()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.child(h_stack().child(div().w_px().h_full()).child(
|
||||
div().w_px().h_full().fill(theme.middle.base.default.border),
|
||||
))
|
||||
}))
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.relative()
|
||||
.children(self.disclosure_control(cx))
|
||||
.children(left_content)
|
||||
.child(self.label.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Element)]
|
||||
pub struct ListSeparator<S: 'static + Send + Sync> {
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> ListSeparator<S> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div().h_px().w_full().fill(theme.lowest.base.default.border)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct List<S: 'static + Send + Sync + Clone> {
|
||||
items: Vec<ListItem<S>>,
|
||||
empty_message: &'static str,
|
||||
header: Option<ListHeader<S>>,
|
||||
toggleable: Toggleable,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync + Clone> List<S> {
|
||||
pub fn new(items: Vec<ListItem<S>>) -> Self {
|
||||
Self {
|
||||
items,
|
||||
empty_message: "No items",
|
||||
header: None,
|
||||
toggleable: Toggleable::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_message(mut self, empty_message: &'static str) -> Self {
|
||||
self.empty_message = empty_message;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, header: ListHeader<S>) -> Self {
|
||||
self.header = Some(header);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
||||
self.toggleable = toggle.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
let token = token();
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||
|
||||
let list_content = match (self.items.is_empty(), is_toggled) {
|
||||
(_, false) => div(),
|
||||
(false, _) => div().children(self.items.iter().cloned()),
|
||||
(true, _) => div().child(Label::new(self.empty_message).color(LabelColor::Muted)),
|
||||
};
|
||||
|
||||
v_stack()
|
||||
.py_1()
|
||||
.children(
|
||||
self.header
|
||||
.clone()
|
||||
.map(|header| header.set_toggleable(self.toggleable)),
|
||||
)
|
||||
.child(list_content)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use gpui3::AbsoluteLength;
|
||||
|
||||
use crate::{prelude::*, theme};
|
||||
use crate::{token, v_stack};
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum PanelAllowedSides {
|
||||
LeftOnly,
|
||||
RightOnly,
|
||||
BottomOnly,
|
||||
#[default]
|
||||
LeftAndRight,
|
||||
All,
|
||||
}
|
||||
|
||||
impl PanelAllowedSides {
|
||||
/// Return a `HashSet` that contains the allowable `PanelSide`s.
|
||||
pub fn allowed_sides(&self) -> HashSet<PanelSide> {
|
||||
match self {
|
||||
Self::LeftOnly => HashSet::from_iter([PanelSide::Left]),
|
||||
Self::RightOnly => HashSet::from_iter([PanelSide::Right]),
|
||||
Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]),
|
||||
Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]),
|
||||
Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum PanelSide {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct Panel<S: 'static + Send + Sync> {
|
||||
state_type: PhantomData<S>,
|
||||
scroll_state: ScrollState,
|
||||
current_side: PanelSide,
|
||||
/// Defaults to PanelAllowedSides::LeftAndRight
|
||||
allowed_sides: PanelAllowedSides,
|
||||
initial_width: AbsoluteLength,
|
||||
width: Option<AbsoluteLength>,
|
||||
children: HackyChildren<S>,
|
||||
payload: HackyChildrenPayload,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> Panel<S> {
|
||||
pub fn new(
|
||||
scroll_state: ScrollState,
|
||||
children: HackyChildren<S>,
|
||||
payload: HackyChildrenPayload,
|
||||
) -> Self {
|
||||
let token = token();
|
||||
|
||||
Self {
|
||||
state_type: PhantomData,
|
||||
scroll_state,
|
||||
current_side: PanelSide::default(),
|
||||
allowed_sides: PanelAllowedSides::default(),
|
||||
initial_width: token.default_panel_size,
|
||||
width: None,
|
||||
children,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self {
|
||||
self.initial_width = initial_width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: AbsoluteLength) -> Self {
|
||||
self.width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self {
|
||||
self.allowed_sides = allowed_sides;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn side(mut self, side: PanelSide) -> Self {
|
||||
let allowed_sides = self.allowed_sides.allowed_sides();
|
||||
|
||||
if allowed_sides.contains(&side) {
|
||||
self.current_side = side;
|
||||
} else {
|
||||
panic!(
|
||||
"The panel side {:?} was not added as allowed before it was set.",
|
||||
side
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let token = token();
|
||||
let theme = theme(cx);
|
||||
|
||||
let panel_base;
|
||||
let current_width = self.width.unwrap_or(self.initial_width);
|
||||
|
||||
match self.current_side {
|
||||
PanelSide::Left => {
|
||||
panel_base = v_stack()
|
||||
.flex_initial()
|
||||
.h_full()
|
||||
// .w(current_width)
|
||||
.w_64()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.border_r()
|
||||
.border_color(theme.middle.base.default.border);
|
||||
}
|
||||
PanelSide::Right => {
|
||||
panel_base = v_stack()
|
||||
.flex_initial()
|
||||
.h_full()
|
||||
// .w(current_width)
|
||||
.w_64()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.border_l()
|
||||
.border_color(theme.middle.base.default.border);
|
||||
}
|
||||
PanelSide::Bottom => {
|
||||
panel_base = v_stack()
|
||||
.flex_initial()
|
||||
.w_full()
|
||||
// .h(current_width)
|
||||
.h_64()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.border_t()
|
||||
.border_color(theme.middle.base.default.border);
|
||||
}
|
||||
}
|
||||
|
||||
panel_base.children_any((self.children)(cx, self.payload.as_ref()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user