text_view: Update Switch, Checkbox, Radio to let label method support TextView. (#649)

- Add `style` method to TextView.
- Add `inline` method to TextView to use default inline style.
This commit is contained in:
Jason Lee 2025-02-24 15:49:54 +08:00 committed by GitHub
parent 9dade704fb
commit 8fa210bc21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 213 additions and 60 deletions

View file

@ -0,0 +1,35 @@
use gpui::*;
use story::{Assets, SwitchStory};
pub struct Example {
story: Entity<SwitchStory>,
}
impl Example {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let story = SwitchStory::view(window, cx);
Self { story }
}
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::new(window, cx))
}
}
impl Render for Example {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().p_4().size_full().child(self.story.clone())
}
}
fn main() {
let app = Application::new().with_assets(Assets);
app.run(move |cx| {
story::init(cx);
cx.activate(true);
story::create_new_window("Switch Example", Example::view, cx);
});
}

View file

@ -9,6 +9,7 @@ use gpui_component::{
label::Label,
radio::{Radio, RadioGroup},
switch::Switch,
text::TextView,
v_flex, ActiveTheme, Disableable as _, Side, Sizable, StyledExt,
};
@ -203,12 +204,18 @@ impl Render for SwitchStory {
v.check3 = !v.check3;
})),
)
.child(div().w(px(300.)).child(
Checkbox::new("longlong-checkbox").label(
"The long long label text, \
it should ellipsis when the text is too long.",
.child(
div().w(px(300.)).child(
Checkbox::new("longlong-checkbox").label(
TextView::markdown(
"longlong-checkbox",
"The **long long label** text, \
it should ellipsis when the text is too long.",
)
.inline(),
),
),
)),
),
),
)
.child(

View file

@ -1,15 +1,14 @@
use crate::{h_flex, v_flex, ActiveTheme, Disableable, IconName, Selectable};
use crate::{h_flex, text::Text, v_flex, ActiveTheme, Disableable, IconName, Selectable};
use gpui::{
div, prelude::FluentBuilder as _, px, relative, svg, App, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _,
Styled as _, Window,
IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, Styled as _, Window,
};
/// A Checkbox element.
#[derive(IntoElement)]
pub struct Checkbox {
id: ElementId,
label: Option<SharedString>,
label: Option<Text>,
checked: bool,
disabled: bool,
on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
@ -26,7 +25,7 @@ impl Checkbox {
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
pub fn label(mut self, label: impl Into<Text>) -> Self {
self.label = Some(label.into());
self
}

View file

@ -1,6 +1,6 @@
use std::rc::Rc;
use crate::{h_flex, v_flex, ActiveTheme, AxisExt, IconName};
use crate::{h_flex, text::Text, v_flex, ActiveTheme, AxisExt, IconName};
use gpui::{
div, prelude::FluentBuilder, relative, svg, App, Axis, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled,
@ -13,7 +13,7 @@ use gpui::{
#[derive(IntoElement)]
pub struct Radio {
id: ElementId,
label: Option<SharedString>,
label: Option<Text>,
checked: bool,
disabled: bool,
on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
@ -30,7 +30,7 @@ impl Radio {
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
pub fn label(mut self, label: impl Into<Text>) -> Self {
self.label = Some(label.into());
self
}
@ -114,24 +114,6 @@ impl RenderOnce for Radio {
}
}
impl From<&'static str> for Radio {
fn from(label: &'static str) -> Self {
Self::new(label).label(label)
}
}
impl From<SharedString> for Radio {
fn from(label: SharedString) -> Self {
Self::new(label.clone()).label(label)
}
}
impl From<String> for Radio {
fn from(label: String) -> Self {
Self::new(SharedString::from(label.clone())).label(SharedString::from(label))
}
}
/// A Radio group element.
#[derive(IntoElement)]
pub struct RadioGroup {
@ -200,6 +182,24 @@ impl RadioGroup {
}
}
impl From<&'static str> for Radio {
fn from(label: &'static str) -> Self {
Self::new(label).label(label)
}
}
impl From<SharedString> for Radio {
fn from(label: SharedString) -> Self {
Self::new(label.clone()).label(label)
}
}
impl From<String> for Radio {
fn from(label: String) -> Self {
Self::new(SharedString::from(label.clone())).label(SharedString::from(label))
}
}
impl RenderOnce for RadioGroup {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let on_change = self.on_change;

View file

@ -1,8 +1,8 @@
use crate::{h_flex, ActiveTheme, Disableable, Side, Sizable, Size};
use crate::{h_flex, text::Text, ActiveTheme, Disableable, Side, Sizable, Size};
use gpui::{
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, App, Element,
ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _,
SharedString, Styled as _, Window,
Styled as _, Window,
};
use std::{cell::RefCell, rc::Rc, time::Duration};
@ -10,7 +10,7 @@ pub struct Switch {
id: ElementId,
checked: bool,
disabled: bool,
label: Option<SharedString>,
label: Option<Text>,
label_side: Side,
on_click: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
size: Size,
@ -35,7 +35,7 @@ impl Switch {
self
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
pub fn label(mut self, label: impl Into<Text>) -> Self {
self.label = Some(label.into());
self
}
@ -193,7 +193,7 @@ impl Element for Switch {
),
),
)
.when_some(self.label.clone(), |this, label| {
.when_some(self.label.take(), |this, label| {
this.child(div().child(label).map(|this| match self.size {
Size::XSmall | Size::Small => this.text_sm(),
_ => this.text_base(),

View file

@ -9,7 +9,7 @@ use gpui::{
use crate::{h_flex, v_flex, ActiveTheme as _, Icon, IconName};
use super::utils::list_item_prefix;
use super::{utils::list_item_prefix, TextViewStyle};
#[allow(unused)]
#[derive(Debug, Default, Clone, PartialEq)]
@ -172,8 +172,10 @@ impl Paragraph {
}
}
/// Ref:
/// https://ui.shadcn.com/docs/components/typography
#[allow(unused)]
#[derive(Debug, Clone, IntoElement, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
Root {
children: Vec<Node>,
@ -317,7 +319,7 @@ impl RenderOnce for Paragraph {
}
#[derive(Default)]
struct ListState {
pub(crate) struct ListState {
todo: bool,
ordered: bool,
depth: usize,
@ -328,6 +330,7 @@ impl Node {
item: Node,
ix: usize,
state: ListState,
text_view_style: &TextViewStyle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
@ -373,24 +376,26 @@ impl Node {
}),
)
})
.child(child.render_node(
.child(child.render(
Some(ListState {
depth: state.depth + 1,
ordered: state.ordered,
todo: checked.is_some(),
}),
text_view_style,
window,
cx,
)),
);
}
Node::List { .. } => {
items.push(div().ml(rems(1.)).child(child.render_node(
items.push(div().ml(rems(1.)).child(child.render(
Some(ListState {
depth: state.depth + 1,
ordered: state.ordered,
todo: checked.is_some(),
}),
text_view_style,
window,
cx,
)))
@ -477,17 +482,28 @@ impl Node {
}
}
fn render_node(
pub(crate) fn render(
self,
list_state: Option<ListState>,
text_view_style: &TextViewStyle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let in_list = list_state.is_some();
let mb = if in_list { rems(0.0) } else { rems(1.) };
let mb = if in_list {
rems(0.)
} else {
text_view_style.paragraph_gap
};
match self {
Node::Root { children } => div().children(children).into_any_element(),
Node::Root { children } => div()
.children(
children
.into_iter()
.map(|c| c.render(None, text_view_style, window, cx)),
)
.into_any_element(),
Node::Paragraph(paragraph) => div().mb(mb).child(paragraph).into_any_element(),
Node::Heading { level, children } => {
let (text_size, font_weight) = match level {
@ -535,6 +551,7 @@ impl Node {
todo: list_state.todo,
depth: list_state.depth,
},
text_view_style,
window,
cx,
));
@ -573,11 +590,3 @@ impl Node {
}
}
}
/// Ref:
/// https://ui.shadcn.com/docs/components/typography
impl RenderOnce for Node {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
self.render_node(None, window, cx)
}
}

View file

@ -19,6 +19,7 @@ use crate::v_flex;
use super::element::{
self, ImageNode, InlineTextStyle, LinkMark, Paragraph, Table, TableRow, TextNode,
};
use super::TextViewStyle;
const BLOCK_ELEMENTS: [&str; 33] = [
"html",
@ -78,9 +79,11 @@ pub(super) fn parse_html(source: &str) -> Result<element::Node, SharedString> {
Ok(node)
}
#[derive(Clone)]
pub(super) struct HtmlElement {
id: ElementId,
text: SharedString,
style: TextViewStyle,
}
impl HtmlElement {
@ -88,6 +91,7 @@ impl HtmlElement {
Self {
id: id.into(),
text: raw.into(),
style: TextViewStyle::default(),
}
}
@ -96,6 +100,12 @@ impl HtmlElement {
self.text = raw.into();
self
}
/// Set TextViewStyle.
pub(crate) fn style(mut self, style: impl Into<TextViewStyle>) -> Self {
self.style = style.into();
self
}
}
#[derive(Default)]
@ -150,7 +160,7 @@ impl Element for HtmlElement {
let mut el = div()
.map(|this| match root {
Ok(node) => this.child(node),
Ok(node) => this.child(node.render(None, &self.style, window, cx)),
Err(err) => this.child(
v_flex()
.gap_1()

View file

@ -12,6 +12,7 @@ use crate::v_flex;
use super::{
element::{self, ImageNode, InlineTextStyle, LinkMark, Paragraph, Span, Table, TableRow},
html::parse_html,
TextViewStyle,
};
/// Markdown GFM renderer
@ -30,9 +31,11 @@ use super::{
/// - As a markdown editor.
/// - Add custom markdown syntax.
/// - Complex styles cumstomization.
#[derive(Clone)]
pub(super) struct MarkdownElement {
id: ElementId,
text: SharedString,
style: TextViewStyle,
}
impl MarkdownElement {
@ -40,6 +43,7 @@ impl MarkdownElement {
Self {
id: id.into(),
text: raw.into(),
style: TextViewStyle::default(),
}
}
@ -48,6 +52,12 @@ impl MarkdownElement {
self.text = raw.into();
self
}
/// Set TextViewStyle.
pub(crate) fn style(mut self, style: impl Into<TextViewStyle>) -> Self {
self.style = style.into();
self
}
}
#[derive(Default)]
@ -106,7 +116,7 @@ impl Element for MarkdownElement {
let mut el = div()
.map(|this| match root {
Ok(node) => this.child(node),
Ok(node) => this.child(node.render(None, &self.style, window, cx)),
Err(err) => this.child(
v_flex()
.gap_1()

View file

@ -1,4 +1,4 @@
use gpui::{App, ElementId, IntoElement, RenderOnce, SharedString, Window};
use gpui::{rems, App, ElementId, IntoElement, Rems, RenderOnce, SharedString, Window};
use html::HtmlElement;
use markdown::MarkdownElement;
@ -7,9 +7,79 @@ mod html;
mod markdown;
mod utils;
#[derive(IntoElement, Clone)]
pub enum Text {
String(SharedString),
TextView(TextView),
}
impl From<SharedString> for Text {
fn from(s: SharedString) -> Self {
Self::String(s)
}
}
impl From<&str> for Text {
fn from(s: &str) -> Self {
Self::String(SharedString::from(s.to_string()))
}
}
impl From<String> for Text {
fn from(s: String) -> Self {
Self::String(s.into())
}
}
impl From<TextView> for Text {
fn from(e: TextView) -> Self {
Self::TextView(e)
}
}
impl RenderOnce for Text {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
match self {
Self::String(s) => s.into_any_element(),
Self::TextView(e) => e.into_any_element(),
}
}
}
/// TextViewStyle used to customize the style for [`TextView`].
#[derive(Copy, Clone)]
pub struct TextViewStyle {
paragraph_gap: Rems,
}
impl Default for TextViewStyle {
fn default() -> Self {
Self {
paragraph_gap: rems(1.),
}
}
}
impl TextViewStyle {
/// Default style for inline text.
///
/// This style has no paragraph gap.
pub fn inline() -> Self {
Self {
paragraph_gap: rems(0.),
}
}
/// Set paragraph gap, default is 1 rem.
pub fn paragraph_gap(mut self, gap: Rems) -> Self {
self.paragraph_gap = gap;
self
}
}
/// A text view that can render Markdown or HTML.
#[allow(private_interfaces)]
#[derive(IntoElement)]
#[derive(IntoElement, Clone)]
pub enum TextView {
Markdown(MarkdownElement),
Html(HtmlElement),
@ -33,6 +103,19 @@ impl TextView {
Self::Html(el) => Self::Html(el.text(raw)),
}
}
/// Set [`TextViewStyle`].
pub fn style(self, style: TextViewStyle) -> Self {
match self {
Self::Markdown(el) => Self::Markdown(el.style(style)),
Self::Html(el) => Self::Html(el.style(style)),
}
}
/// Set to use [`TextViewStyle::inline`].
pub fn inline(self) -> Self {
self.style(TextViewStyle::inline())
}
}
impl RenderOnce for TextView {

View file

@ -1,17 +1,17 @@
use gpui::{
div, prelude::FluentBuilder, px, AnyElement, AnyView, App, AppContext, Context, IntoElement,
ParentElement, Render, SharedString, Styled, Window,
ParentElement, Render, Styled, Window,
};
use crate::ActiveTheme;
use crate::{text::Text, ActiveTheme};
pub struct Tooltip {
text: SharedString,
text: Text,
element_builder: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
}
impl Tooltip {
pub fn new(text: impl Into<SharedString>, _: &mut Window, cx: &mut App) -> AnyView {
pub fn new(text: impl Into<Text>, _: &mut Window, cx: &mut App) -> AnyView {
cx.new(|_| Self {
text: text.into(),
element_builder: None,