parent
2234f91b7b
commit
555692fac6
5 changed files with 269 additions and 49 deletions
|
@ -44,7 +44,7 @@ pub struct ClaudeCode;
|
||||||
|
|
||||||
impl AgentServer for ClaudeCode {
|
impl AgentServer for ClaudeCode {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"Claude Code"
|
"Welcome to Claude Code"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_state_headline(&self) -> &'static str {
|
fn empty_state_headline(&self) -> &'static str {
|
||||||
|
|
|
@ -41,7 +41,7 @@ use text::Anchor;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
Callout, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
Callout, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
||||||
Scrollbar, ScrollbarState, Tooltip, prelude::*,
|
Scrollbar, ScrollbarState, SpinnerLabel, Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
||||||
use workspace::{CollaboratorId, Workspace};
|
use workspace::{CollaboratorId, Workspace};
|
||||||
|
@ -1205,7 +1205,7 @@ impl AcpThreadView {
|
||||||
div()
|
div()
|
||||||
.py_3()
|
.py_3()
|
||||||
.px_2()
|
.px_2()
|
||||||
.rounded_lg()
|
.rounded_md()
|
||||||
.shadow_md()
|
.shadow_md()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.border_1()
|
.border_1()
|
||||||
|
@ -1263,7 +1263,7 @@ impl AcpThreadView {
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) => {
|
AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) => {
|
||||||
let style = default_markdown_style(false, window, cx);
|
let style = default_markdown_style(false, false, window, cx);
|
||||||
let message_body = v_flex()
|
let message_body = v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_2p5()
|
.gap_2p5()
|
||||||
|
@ -1398,8 +1398,6 @@ impl AcpThreadView {
|
||||||
.relative()
|
.relative()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.opacity(0.8)
|
|
||||||
.hover(|style| style.opacity(1.))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.size_4()
|
.size_4()
|
||||||
|
@ -1440,6 +1438,7 @@ impl AcpThreadView {
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(self.tool_name_font_size())
|
.text_size(self.tool_name_font_size())
|
||||||
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.child("Thinking"),
|
.child("Thinking"),
|
||||||
)
|
)
|
||||||
.on_click(cx.listener({
|
.on_click(cx.listener({
|
||||||
|
@ -1463,9 +1462,10 @@ impl AcpThreadView {
|
||||||
.border_l_1()
|
.border_l_1()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm(cx)
|
||||||
.child(
|
.child(self.render_markdown(
|
||||||
self.render_markdown(chunk, default_markdown_style(false, window, cx)),
|
chunk,
|
||||||
),
|
default_markdown_style(false, false, window, cx),
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
@ -1555,11 +1555,11 @@ impl AcpThreadView {
|
||||||
| ToolCallStatus::Completed => None,
|
| ToolCallStatus::Completed => None,
|
||||||
ToolCallStatus::InProgress => Some(
|
ToolCallStatus::InProgress => Some(
|
||||||
Icon::new(IconName::ArrowCircle)
|
Icon::new(IconName::ArrowCircle)
|
||||||
.color(Color::Accent)
|
.color(Color::Muted)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
.with_animation(
|
.with_animation(
|
||||||
"running",
|
"running",
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
Animation::new(Duration::from_secs(3)).repeat(),
|
||||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
)
|
)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
|
@ -1572,6 +1572,10 @@ impl AcpThreadView {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let failed_tool_call = matches!(
|
||||||
|
tool_call.status,
|
||||||
|
ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed
|
||||||
|
);
|
||||||
let needs_confirmation = matches!(
|
let needs_confirmation = matches!(
|
||||||
tool_call.status,
|
tool_call.status,
|
||||||
ToolCallStatus::WaitingForConfirmation { .. }
|
ToolCallStatus::WaitingForConfirmation { .. }
|
||||||
|
@ -1652,7 +1656,7 @@ impl AcpThreadView {
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.when(use_card_layout, |this| {
|
.when(use_card_layout, |this| {
|
||||||
this.rounded_lg()
|
this.rounded_md()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
@ -1664,20 +1668,16 @@ impl AcpThreadView {
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.map(|this| {
|
.when(use_card_layout, |this| {
|
||||||
if use_card_layout {
|
this.pl_2()
|
||||||
this.pl_2()
|
.pr_1p5()
|
||||||
.pr_1p5()
|
.py_1()
|
||||||
.py_1()
|
.rounded_t_md()
|
||||||
.rounded_t_md()
|
.when(is_open && !failed_tool_call, |this| {
|
||||||
.when(is_open, |this| {
|
this.border_b_1()
|
||||||
this.border_b_1()
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.border_color(self.tool_card_border_color(cx))
|
})
|
||||||
})
|
.bg(self.tool_card_header_bg(cx))
|
||||||
.bg(self.tool_card_header_bg(cx))
|
|
||||||
} else {
|
|
||||||
this.opacity(0.8).hover(|style| style.opacity(1.))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -1709,13 +1709,15 @@ impl AcpThreadView {
|
||||||
.px_1p5()
|
.px_1p5()
|
||||||
.rounded_sm()
|
.rounded_sm()
|
||||||
.overflow_x_scroll()
|
.overflow_x_scroll()
|
||||||
.opacity(0.8)
|
|
||||||
.hover(|label| {
|
.hover(|label| {
|
||||||
label.opacity(1.).bg(cx
|
label.bg(cx.theme().colors().element_hover.opacity(0.5))
|
||||||
.theme()
|
})
|
||||||
.colors()
|
.map(|this| {
|
||||||
.element_hover
|
if use_card_layout {
|
||||||
.opacity(0.5))
|
this.text_color(cx.theme().colors().text)
|
||||||
|
} else {
|
||||||
|
this.text_color(cx.theme().colors().text_muted)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.child(name)
|
.child(name)
|
||||||
.tooltip(Tooltip::text("Jump to File"))
|
.tooltip(Tooltip::text("Jump to File"))
|
||||||
|
@ -1738,7 +1740,7 @@ impl AcpThreadView {
|
||||||
.overflow_x_scroll()
|
.overflow_x_scroll()
|
||||||
.child(self.render_markdown(
|
.child(self.render_markdown(
|
||||||
tool_call.label.clone(),
|
tool_call.label.clone(),
|
||||||
default_markdown_style(false, window, cx),
|
default_markdown_style(false, true, window, cx),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.child(gradient_overlay(gradient_color))
|
.child(gradient_overlay(gradient_color))
|
||||||
|
@ -1804,9 +1806,9 @@ impl AcpThreadView {
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.child(self.render_markdown(markdown, default_markdown_style(false, window, cx)))
|
.child(self.render_markdown(markdown, default_markdown_style(false, false, window, cx)))
|
||||||
.child(
|
.child(
|
||||||
Button::new(button_id, "Collapse Output")
|
Button::new(button_id, "Collapse")
|
||||||
.full_width()
|
.full_width()
|
||||||
.style(ButtonStyle::Outlined)
|
.style(ButtonStyle::Outlined)
|
||||||
.label_size(LabelSize::Small)
|
.label_size(LabelSize::Small)
|
||||||
|
@ -2131,7 +2133,7 @@ impl AcpThreadView {
|
||||||
.to_string()
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"Output is {} long—to avoid unexpected token usage, \
|
"Output is {} long, and to avoid unexpected token usage, \
|
||||||
only 16 KB was sent back to the model.",
|
only 16 KB was sent back to the model.",
|
||||||
format_file_size(output.original_content_len as u64, true),
|
format_file_size(output.original_content_len as u64, true),
|
||||||
)
|
)
|
||||||
|
@ -2199,7 +2201,7 @@ impl AcpThreadView {
|
||||||
.border_1()
|
.border_1()
|
||||||
.when(tool_failed || command_failed, |card| card.border_dashed())
|
.when(tool_failed || command_failed, |card| card.border_dashed())
|
||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.rounded_lg()
|
.rounded_md()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
@ -2553,9 +2555,10 @@ impl AcpThreadView {
|
||||||
.into_any(),
|
.into_any(),
|
||||||
)
|
)
|
||||||
.children(description.map(|desc| {
|
.children(description.map(|desc| {
|
||||||
div().text_ui(cx).text_center().child(
|
div().text_ui(cx).text_center().child(self.render_markdown(
|
||||||
self.render_markdown(desc.clone(), default_markdown_style(false, window, cx)),
|
desc.clone(),
|
||||||
)
|
default_markdown_style(false, false, window, cx),
|
||||||
|
))
|
||||||
}))
|
}))
|
||||||
.children(
|
.children(
|
||||||
configuration_view
|
configuration_view
|
||||||
|
@ -3379,7 +3382,7 @@ impl AcpThreadView {
|
||||||
"used-tokens-label",
|
"used-tokens-label",
|
||||||
Animation::new(Duration::from_secs(2))
|
Animation::new(Duration::from_secs(2))
|
||||||
.repeat()
|
.repeat()
|
||||||
.with_easing(pulsating_between(0.6, 1.)),
|
.with_easing(pulsating_between(0.3, 0.8)),
|
||||||
|label, delta| label.alpha(delta),
|
|label, delta| label.alpha(delta),
|
||||||
)
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
|
@ -4636,9 +4639,9 @@ impl Render for AcpThreadView {
|
||||||
ThreadStatus::Idle
|
ThreadStatus::Idle
|
||||||
| ThreadStatus::WaitingForToolConfirmation => None,
|
| ThreadStatus::WaitingForToolConfirmation => None,
|
||||||
ThreadStatus::Generating => div()
|
ThreadStatus::Generating => div()
|
||||||
.px_5()
|
|
||||||
.py_2()
|
.py_2()
|
||||||
.child(LoadingLabel::new("").size(LabelSize::Small))
|
.px(rems_from_px(22.))
|
||||||
|
.child(SpinnerLabel::new().size(LabelSize::Small))
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -4671,7 +4674,12 @@ impl Render for AcpThreadView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_markdown_style(buffer_font: bool, window: &Window, cx: &App) -> MarkdownStyle {
|
fn default_markdown_style(
|
||||||
|
buffer_font: bool,
|
||||||
|
muted_text: bool,
|
||||||
|
window: &Window,
|
||||||
|
cx: &App,
|
||||||
|
) -> MarkdownStyle {
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
|
|
||||||
|
@ -4692,20 +4700,26 @@ fn default_markdown_style(buffer_font: bool, window: &Window, cx: &App) -> Markd
|
||||||
TextSize::Default.rems(cx)
|
TextSize::Default.rems(cx)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let text_color = if muted_text {
|
||||||
|
colors.text_muted
|
||||||
|
} else {
|
||||||
|
colors.text
|
||||||
|
};
|
||||||
|
|
||||||
text_style.refine(&TextStyleRefinement {
|
text_style.refine(&TextStyleRefinement {
|
||||||
font_family: Some(font_family),
|
font_family: Some(font_family),
|
||||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||||
font_features: Some(theme_settings.ui_font.features.clone()),
|
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||||
font_size: Some(font_size.into()),
|
font_size: Some(font_size.into()),
|
||||||
line_height: Some(line_height.into()),
|
line_height: Some(line_height.into()),
|
||||||
color: Some(cx.theme().colors().text),
|
color: Some(text_color),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
MarkdownStyle {
|
MarkdownStyle {
|
||||||
base_text_style: text_style.clone(),
|
base_text_style: text_style.clone(),
|
||||||
syntax: cx.theme().syntax().clone(),
|
syntax: cx.theme().syntax().clone(),
|
||||||
selection_background_color: cx.theme().colors().element_selection_background,
|
selection_background_color: colors.element_selection_background,
|
||||||
code_block_overflow_x_scroll: true,
|
code_block_overflow_x_scroll: true,
|
||||||
table_overflow_x_scroll: true,
|
table_overflow_x_scroll: true,
|
||||||
heading_level_styles: Some(HeadingLevelStyles {
|
heading_level_styles: Some(HeadingLevelStyles {
|
||||||
|
@ -4791,7 +4805,7 @@ fn plan_label_markdown_style(
|
||||||
window: &Window,
|
window: &Window,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> MarkdownStyle {
|
) -> MarkdownStyle {
|
||||||
let default_md_style = default_markdown_style(false, window, cx);
|
let default_md_style = default_markdown_style(false, false, window, cx);
|
||||||
|
|
||||||
MarkdownStyle {
|
MarkdownStyle {
|
||||||
base_text_style: TextStyle {
|
base_text_style: TextStyle {
|
||||||
|
@ -4811,7 +4825,7 @@ fn plan_label_markdown_style(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||||
let default_md_style = default_markdown_style(true, window, cx);
|
let default_md_style = default_markdown_style(true, false, window, cx);
|
||||||
|
|
||||||
MarkdownStyle {
|
MarkdownStyle {
|
||||||
base_text_style: TextStyle {
|
base_text_style: TextStyle {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gpui::{ClickEvent, CursorStyle};
|
use gpui::{ClickEvent, CursorStyle, SharedString};
|
||||||
|
|
||||||
use crate::{Color, IconButton, IconButtonShape, IconName, IconSize, prelude::*};
|
use crate::{Color, IconButton, IconButtonShape, IconName, IconSize, prelude::*};
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ pub struct Disclosure {
|
||||||
cursor_style: CursorStyle,
|
cursor_style: CursorStyle,
|
||||||
opened_icon: IconName,
|
opened_icon: IconName,
|
||||||
closed_icon: IconName,
|
closed_icon: IconName,
|
||||||
|
visible_on_hover: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Disclosure {
|
impl Disclosure {
|
||||||
|
@ -27,6 +28,7 @@ impl Disclosure {
|
||||||
cursor_style: CursorStyle::PointingHand,
|
cursor_style: CursorStyle::PointingHand,
|
||||||
opened_icon: IconName::ChevronDown,
|
opened_icon: IconName::ChevronDown,
|
||||||
closed_icon: IconName::ChevronRight,
|
closed_icon: IconName::ChevronRight,
|
||||||
|
visible_on_hover: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +75,13 @@ impl Clickable for Disclosure {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VisibleOnHover for Disclosure {
|
||||||
|
fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
|
||||||
|
self.visible_on_hover = Some(group_name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Disclosure {
|
impl RenderOnce for Disclosure {
|
||||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
|
@ -87,6 +96,9 @@ impl RenderOnce for Disclosure {
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.disabled(self.disabled)
|
.disabled(self.disabled)
|
||||||
.toggle_state(self.selected)
|
.toggle_state(self.selected)
|
||||||
|
.when_some(self.visible_on_hover.clone(), |this, group_name| {
|
||||||
|
this.visible_on_hover(group_name)
|
||||||
|
})
|
||||||
.when_some(self.on_toggle, move |this, on_toggle| {
|
.when_some(self.on_toggle, move |this, on_toggle| {
|
||||||
this.on_click(move |event, window, cx| on_toggle(event, window, cx))
|
this.on_click(move |event, window, cx| on_toggle(event, window, cx))
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,8 +2,10 @@ mod highlighted_label;
|
||||||
mod label;
|
mod label;
|
||||||
mod label_like;
|
mod label_like;
|
||||||
mod loading_label;
|
mod loading_label;
|
||||||
|
mod spinner_label;
|
||||||
|
|
||||||
pub use highlighted_label::*;
|
pub use highlighted_label::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
pub use label_like::*;
|
pub use label_like::*;
|
||||||
pub use loading_label::*;
|
pub use loading_label::*;
|
||||||
|
pub use spinner_label::*;
|
||||||
|
|
192
crates/ui/src/components/label/spinner_label.rs
Normal file
192
crates/ui/src/components/label/spinner_label.rs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use gpui::{Animation, AnimationExt, FontWeight};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Different types of spinner animations
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
|
pub enum SpinnerVariant {
|
||||||
|
#[default]
|
||||||
|
Dots,
|
||||||
|
DotsVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A spinner indication, based on the label component, that loops through
|
||||||
|
/// frames of the specified animation. It implements `LabelCommon` as well.
|
||||||
|
///
|
||||||
|
/// # Default Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use ui::{SpinnerLabel};
|
||||||
|
///
|
||||||
|
/// SpinnerLabel::new();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Variant Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use ui::{SpinnerLabel};
|
||||||
|
///
|
||||||
|
/// SpinnerLabel::dots_variant();
|
||||||
|
/// ```
|
||||||
|
#[derive(IntoElement, RegisterComponent)]
|
||||||
|
pub struct SpinnerLabel {
|
||||||
|
base: Label,
|
||||||
|
variant: SpinnerVariant,
|
||||||
|
frames: Vec<&'static str>,
|
||||||
|
duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpinnerVariant {
|
||||||
|
fn frames(&self) -> Vec<&'static str> {
|
||||||
|
match self {
|
||||||
|
SpinnerVariant::Dots => vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
||||||
|
SpinnerVariant::DotsVariant => vec!["⣼", "⣹", "⢻", "⠿", "⡟", "⣏", "⣧", "⣶"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration(&self) -> Duration {
|
||||||
|
match self {
|
||||||
|
SpinnerVariant::Dots => Duration::from_millis(1000),
|
||||||
|
SpinnerVariant::DotsVariant => Duration::from_millis(1000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn animation_id(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SpinnerVariant::Dots => "spinner_label_dots",
|
||||||
|
SpinnerVariant::DotsVariant => "spinner_label_dots_variant",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpinnerLabel {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::with_variant(SpinnerVariant::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_variant(variant: SpinnerVariant) -> Self {
|
||||||
|
let frames = variant.frames();
|
||||||
|
let duration = variant.duration();
|
||||||
|
|
||||||
|
SpinnerLabel {
|
||||||
|
base: Label::new(frames[0]),
|
||||||
|
variant,
|
||||||
|
frames,
|
||||||
|
duration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dots() -> Self {
|
||||||
|
Self::with_variant(SpinnerVariant::Dots)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dots_variant() -> Self {
|
||||||
|
Self::with_variant(SpinnerVariant::DotsVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelCommon for SpinnerLabel {
|
||||||
|
fn size(mut self, size: LabelSize) -> Self {
|
||||||
|
self.base = self.base.size(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn weight(mut self, weight: FontWeight) -> Self {
|
||||||
|
self.base = self.base.weight(weight);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
|
||||||
|
self.base = self.base.line_height_style(line_height_style);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(mut self, color: Color) -> Self {
|
||||||
|
self.base = self.base.color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strikethrough(mut self) -> Self {
|
||||||
|
self.base = self.base.strikethrough();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn italic(mut self) -> Self {
|
||||||
|
self.base = self.base.italic();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alpha(mut self, alpha: f32) -> Self {
|
||||||
|
self.base = self.base.alpha(alpha);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn underline(mut self) -> Self {
|
||||||
|
self.base = self.base.underline();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(mut self) -> Self {
|
||||||
|
self.base = self.base.truncate();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn single_line(mut self) -> Self {
|
||||||
|
self.base = self.base.single_line();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_font(mut self, cx: &App) -> Self {
|
||||||
|
self.base = self.base.buffer_font(cx);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inline_code(mut self, cx: &App) -> Self {
|
||||||
|
self.base = self.base.inline_code(cx);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for SpinnerLabel {
|
||||||
|
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let frames = self.frames.clone();
|
||||||
|
let duration = self.duration;
|
||||||
|
|
||||||
|
self.base.color(Color::Muted).with_animation(
|
||||||
|
self.variant.animation_id(),
|
||||||
|
Animation::new(duration).repeat(),
|
||||||
|
move |mut label, delta| {
|
||||||
|
let frame_index = (delta * frames.len() as f32) as usize % frames.len();
|
||||||
|
|
||||||
|
label.set_text(frames[frame_index]);
|
||||||
|
label
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for SpinnerLabel {
|
||||||
|
fn scope() -> ComponentScope {
|
||||||
|
ComponentScope::Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"Spinner Label"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_name() -> &'static str {
|
||||||
|
"Spinner Label"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||||
|
let examples = vec![
|
||||||
|
single_example("Default", SpinnerLabel::new().into_any_element()),
|
||||||
|
single_example(
|
||||||
|
"Dots Variant",
|
||||||
|
SpinnerLabel::dots_variant().into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Some(example_group(examples).vertical().into_any_element())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue