Compare commits
2 Commits
github-tok
...
agent-bann
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7cb23fe78 | ||
|
|
c294b4d0b8 |
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -3197,6 +3197,7 @@ dependencies = [
|
||||
"serde",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"ui_parking_lot",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
@@ -15487,6 +15488,19 @@ dependencies = [
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ui_parking_lot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"component",
|
||||
"gpui",
|
||||
"linkme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ui_prompt"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -158,6 +158,7 @@ members = [
|
||||
"crates/title_bar",
|
||||
"crates/toolchain_selector",
|
||||
"crates/ui",
|
||||
"crates/ui_parking_lot",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
"crates/ui_prompt",
|
||||
@@ -365,6 +366,7 @@ toolchain_selector = { path = "crates/toolchain_selector" }
|
||||
ui = { path = "crates/ui" }
|
||||
ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
ui_parking_lot = { path = "crates/ui_parking_lot" }
|
||||
ui_prompt = { path = "crates/ui_prompt" }
|
||||
util = { path = "crates/util" }
|
||||
util_macros = { path = "crates/util_macros" }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
@@ -15,9 +16,20 @@ pub trait Component {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
|
||||
fn init_component(_weak_workspace: Box<dyn Any>) {}
|
||||
|
||||
// In theory we could downcast to a WeakEntity<Workspace> and use that to build
|
||||
// whatever you need to initialize the component, but I haven't tested it yet.
|
||||
//
|
||||
// fn init_component(weak_workspace: Box<dyn Any>) {
|
||||
// let weak_workspace = weak_workspace.downcast::<WeakEntity<Workspace>>().unwrap();
|
||||
// }
|
||||
|
||||
fn name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
/// Returns a name that the component should be sorted by.
|
||||
///
|
||||
/// Implement this if the component should be sorted in an alternate order than its name.
|
||||
|
||||
@@ -23,6 +23,7 @@ languages.workspace = true
|
||||
notifications.workspace = true
|
||||
project.workspace = true
|
||||
ui.workspace = true
|
||||
ui_parking_lot.workspace = true
|
||||
ui_input.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -26,6 +26,9 @@ use ui_input::SingleLineInput;
|
||||
use workspace::{AppState, ItemId, SerializableItem};
|
||||
use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
|
||||
|
||||
#[allow(unused)]
|
||||
use ui_parking_lot::*; // Import for component registry, gets culled otherwise
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
||||
workspace::register_serializable_item::<ComponentPreview>(cx);
|
||||
|
||||
|
||||
@@ -110,7 +110,11 @@ impl RenderOnce for Banner {
|
||||
|
||||
let mut container = base.bg(bg_color).border_color(border_color);
|
||||
|
||||
let mut content_area = h_flex().id("content_area").gap_1p5().overflow_x_scroll();
|
||||
let mut content_area = h_flex()
|
||||
.id("content_area")
|
||||
.flex_1()
|
||||
.gap_1p5()
|
||||
.overflow_x_scroll();
|
||||
|
||||
if self.icon.is_none() {
|
||||
content_area =
|
||||
@@ -126,10 +130,13 @@ impl RenderOnce for Banner {
|
||||
.pl_2()
|
||||
.pr_0p5()
|
||||
.gap_2()
|
||||
.flex_1()
|
||||
.child(content_area)
|
||||
.child(action_slot);
|
||||
} else {
|
||||
container = container.px_2().child(div().w_full().child(content_area));
|
||||
container = container
|
||||
.px_2()
|
||||
.child(div().w_full().flex_1().child(content_area));
|
||||
}
|
||||
|
||||
container
|
||||
|
||||
24
crates/ui_parking_lot/Cargo.toml
Normal file
24
crates/ui_parking_lot/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "ui_parking_lot"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/ui_parking_lot.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
component.workspace = true
|
||||
gpui.workspace = true
|
||||
linkme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
1
crates/ui_parking_lot/LICENSE-GPL
Symbolic link
1
crates/ui_parking_lot/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
1
crates/ui_parking_lot/src/agent/mod.rs
Normal file
1
crates/ui_parking_lot/src/agent/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod usage_banner;
|
||||
475
crates/ui_parking_lot/src/agent/usage_banner.rs
Normal file
475
crates/ui_parking_lot/src/agent/usage_banner.rs
Normal file
@@ -0,0 +1,475 @@
|
||||
use gpui::Entity;
|
||||
use ui::{Banner, Severity};
|
||||
use ui::{ProgressBar, prelude::*};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CurrentPlan {
|
||||
Trial,
|
||||
Free,
|
||||
Paid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CapReason {
|
||||
RequestLimit,
|
||||
SpendLimit,
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct UsageBanner {
|
||||
current_plan: CurrentPlan,
|
||||
current_requests: u32,
|
||||
current_spend: u32,
|
||||
monthly_cap: u32,
|
||||
usage_based_enabled: bool,
|
||||
usage_progress: Entity<ProgressBar>,
|
||||
}
|
||||
|
||||
impl UsageBanner {
|
||||
/// Creates a new UsageBanner with the provided values
|
||||
pub fn new(
|
||||
current_plan: CurrentPlan,
|
||||
current_requests: u32,
|
||||
current_spend: u32,
|
||||
monthly_cap: u32,
|
||||
usage_based_enabled: bool,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let usage_progress = cx.new(|cx| {
|
||||
ProgressBar::new(
|
||||
"usage_progress",
|
||||
current_requests as f32,
|
||||
request_cap_for_plan(¤t_plan) as f32,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let banner = Self {
|
||||
current_plan,
|
||||
current_requests,
|
||||
current_spend,
|
||||
monthly_cap,
|
||||
usage_based_enabled,
|
||||
usage_progress,
|
||||
};
|
||||
|
||||
// No need to update styling here as it will be done when rendering
|
||||
banner
|
||||
}
|
||||
|
||||
/// Returns the request cap based on the current plan
|
||||
pub fn request_cap(&self) -> u32 {
|
||||
request_cap_for_plan(&self.current_plan)
|
||||
}
|
||||
|
||||
/// Check if the user is capped due to hitting request limits
|
||||
pub fn is_capped_by_requests(&self) -> bool {
|
||||
self.current_requests >= self.request_cap()
|
||||
}
|
||||
|
||||
/// Check if the user is capped due to hitting spend limits
|
||||
pub fn is_capped_by_spend(&self) -> bool {
|
||||
// Only check spend limit if spending is enabled and cap is set
|
||||
self.usage_based_enabled && self.monthly_cap > 0 && self.current_spend >= self.monthly_cap
|
||||
}
|
||||
|
||||
/// Check if the user is approaching request limit (>=90%)
|
||||
pub fn is_approaching_request_limit(&self) -> bool {
|
||||
let threshold = (self.request_cap() as f32 * 0.9) as u32;
|
||||
self.current_requests >= threshold && self.current_requests < self.request_cap()
|
||||
}
|
||||
|
||||
/// Check if the user is approaching spend limit (>=90%)
|
||||
pub fn is_approaching_spend_limit(&self) -> bool {
|
||||
// Only check if spending is enabled and cap is set
|
||||
self.usage_based_enabled
|
||||
&& self.monthly_cap > 0
|
||||
&& self.current_spend >= (self.monthly_cap as f32 * 0.9) as u32
|
||||
&& self.current_spend < self.monthly_cap
|
||||
}
|
||||
|
||||
/// Check if the user is capped and returns the reason
|
||||
pub fn cap_status(&self) -> Option<CapReason> {
|
||||
if self.is_capped_by_requests() {
|
||||
Some(CapReason::RequestLimit)
|
||||
} else if self.is_capped_by_spend() {
|
||||
Some(CapReason::SpendLimit)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the user is capped for any reason
|
||||
pub fn is_capped(&self) -> bool {
|
||||
matches!(
|
||||
self.cap_status(),
|
||||
Some(CapReason::RequestLimit | CapReason::SpendLimit)
|
||||
)
|
||||
}
|
||||
|
||||
/// Update the current request count and progress bar
|
||||
pub fn update_requests(&mut self, requests: u32, cx: &mut Context<Self>) {
|
||||
self.current_requests = requests;
|
||||
self.update_progress_bar(cx);
|
||||
self.update_progress_styling(cx);
|
||||
}
|
||||
|
||||
/// Update the current spend amount
|
||||
pub fn update_spend(&mut self, spend: u32, cx: &mut Context<Self>) {
|
||||
self.current_spend = spend;
|
||||
self.update_progress_bar(cx);
|
||||
self.update_progress_styling(cx);
|
||||
}
|
||||
|
||||
/// Update the progress bar styling based on current usage levels
|
||||
fn update_progress_styling(&self, cx: &mut Context<Self>) {
|
||||
let is_near_cap = self.current_requests as f32 >= self.request_cap() as f32 * 0.9;
|
||||
let is_capped = self.is_capped();
|
||||
|
||||
self.usage_progress.update(cx, |progress_bar, cx| {
|
||||
if is_capped {
|
||||
progress_bar.fg_color(cx.theme().status().error);
|
||||
} else if is_near_cap {
|
||||
progress_bar.fg_color(cx.theme().status().warning);
|
||||
} else {
|
||||
progress_bar.fg_color(cx.theme().status().info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn should_show_request_progress(&self) -> bool {
|
||||
// Show request progress for all plans as long as not capped
|
||||
// Only show if we have a non-zero request cap
|
||||
self.request_cap() > 0 && !self.is_capped_by_requests() && !self.is_capped_by_spend()
|
||||
}
|
||||
|
||||
/// Show the spend progress bar once requests are capped
|
||||
/// if the user has usage based enabled
|
||||
fn should_show_spend_progress(&self) -> bool {
|
||||
// Only show spend progress for paid plans with usage-based pricing enabled
|
||||
// and when a monthly cap is set
|
||||
self.current_plan == CurrentPlan::Paid
|
||||
&& self.usage_based_enabled
|
||||
&& self.monthly_cap > 0
|
||||
&& self.is_capped_by_requests()
|
||||
&& !self.is_capped_by_spend()
|
||||
}
|
||||
|
||||
/// Update the progress bar with current values
|
||||
fn update_progress_bar(&mut self, cx: &mut Context<Self>) {
|
||||
// Update the progress bar with new values
|
||||
// We need to recreate it to update both value and max
|
||||
self.usage_progress.update(cx, |progress_bar, cx| {
|
||||
// Update progress bar value
|
||||
*progress_bar = ProgressBar::new(
|
||||
"usage_progress",
|
||||
self.current_requests as f32,
|
||||
self.request_cap() as f32,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
if self.is_capped_by_spend() || self.is_capped_by_requests() {
|
||||
return Severity::Error;
|
||||
}
|
||||
|
||||
if self.is_approaching_request_limit() || self.is_approaching_spend_limit() {
|
||||
return Severity::Warning;
|
||||
}
|
||||
|
||||
Severity::Info
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to get the request cap based on plan type
|
||||
fn request_cap_for_plan(plan: &CurrentPlan) -> u32 {
|
||||
match plan {
|
||||
CurrentPlan::Trial => 150,
|
||||
CurrentPlan::Free => 50,
|
||||
CurrentPlan::Paid => 500,
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for UsageBanner {
|
||||
fn render(&mut self, _: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let formatted_requests = format!("{} / {}", self.current_requests, self.request_cap());
|
||||
let formatted_spend = format!(
|
||||
"${:.2} / ${:.2}",
|
||||
self.current_spend as f32 / 100.0,
|
||||
if self.monthly_cap > 0 {
|
||||
self.monthly_cap as f32 / 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
);
|
||||
|
||||
let (message, action_button) = if self.is_capped_by_spend() {
|
||||
(
|
||||
"Monthly spending limit reached",
|
||||
Some(Button::new("manage", "Manage Spending").into_any_element()),
|
||||
)
|
||||
} else if self.is_capped_by_requests() {
|
||||
let msg = match self.current_plan {
|
||||
CurrentPlan::Trial => "Trial request limit reached",
|
||||
CurrentPlan::Free => "Free tier request limit reached",
|
||||
CurrentPlan::Paid => "Monthly request limit reached",
|
||||
};
|
||||
|
||||
let action = match self.current_plan {
|
||||
CurrentPlan::Trial | CurrentPlan::Free => {
|
||||
Some(Button::new("upgrade", "Upgrade").into_any_element())
|
||||
}
|
||||
CurrentPlan::Paid => {
|
||||
if self.usage_based_enabled {
|
||||
Some(Button::new("manage", "Manage").into_any_element())
|
||||
} else {
|
||||
Some(Button::new("enable-usage", "Try Usaged-Based").into_any_element())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(msg, action)
|
||||
} else if self.is_approaching_request_limit() {
|
||||
let msg = "Approaching request limit";
|
||||
|
||||
let action = match self.current_plan {
|
||||
CurrentPlan::Trial | CurrentPlan::Free => {
|
||||
Some(Button::new("upgrade", "Upgrade").into_any_element())
|
||||
}
|
||||
CurrentPlan::Paid => {
|
||||
if !self.usage_based_enabled {
|
||||
Some(Button::new("enable-usage", "Manage").into_any_element())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(msg, action)
|
||||
} else if self.is_approaching_spend_limit() {
|
||||
(
|
||||
"Approaching monthly spend limit",
|
||||
Some(Button::new("manage", "Manage Spending").into_any_element()),
|
||||
)
|
||||
} else {
|
||||
let msg = match self.current_plan {
|
||||
CurrentPlan::Trial => "Zed AI Trial",
|
||||
CurrentPlan::Free => "Zed AI Free",
|
||||
CurrentPlan::Paid => "Zed AI Paid",
|
||||
};
|
||||
|
||||
(msg, None)
|
||||
};
|
||||
|
||||
// Build the content section with usage information
|
||||
let mut content = h_flex().flex_1().gap_1().child(Label::new(message));
|
||||
|
||||
// Add usage progress section if we should show it
|
||||
if self.should_show_request_progress() {
|
||||
content = content.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.justify_end()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.w_full()
|
||||
.max_w(px(180.))
|
||||
.child(self.usage_progress.clone()),
|
||||
)
|
||||
.child(
|
||||
Label::new(formatted_requests)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add spending information for Paid users with usage-based pricing
|
||||
if self.should_show_spend_progress() {
|
||||
content = content.child(
|
||||
h_flex().flex_1().justify_end().gap_1p5().child(
|
||||
Label::new(formatted_spend)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Create the banner with appropriate severity and content
|
||||
let mut banner = Banner::new().severity(self.severity()).children(content);
|
||||
|
||||
// Add action button if available
|
||||
if let Some(action) = action_button {
|
||||
banner = banner.action_slot(action);
|
||||
}
|
||||
|
||||
banner
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for UsageBanner {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
// Create instances of UsageBanner for different scenarios
|
||||
// Trial plan examples (cap = 150)
|
||||
let new_trial_user = cx.new(|cx| UsageBanner::new(CurrentPlan::Trial, 10, 0, 0, false, cx));
|
||||
let trial_user_warning =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Trial, 135, 0, 0, false, cx));
|
||||
let trial_user_capped =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Trial, 150, 0, 0, false, cx));
|
||||
|
||||
// Free plan examples (cap = 50)
|
||||
let free_user = cx.new(|cx| UsageBanner::new(CurrentPlan::Free, 25, 0, 0, false, cx));
|
||||
let free_user_warning =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Free, 45, 0, 0, false, cx));
|
||||
let free_user_capped =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Free, 50, 0, 0, false, cx));
|
||||
|
||||
// Pro plan examples without usage-based pricing (cap = 500)
|
||||
let paid_user = cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 250, 0, 0, false, cx));
|
||||
let paid_user_warning =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 450, 0, 0, false, cx));
|
||||
let paid_user_capped =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 0, 0, false, cx));
|
||||
|
||||
// Pro plan examples with usage-based pricing and monthly spend cap (cap = 500)
|
||||
let paid_user_usage_based =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 5000, 20000, true, cx));
|
||||
let paid_user_usage_based_warning =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 18000, 20000, true, cx));
|
||||
let paid_user_usage_based_capped =
|
||||
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 20000, 20000, true, cx));
|
||||
|
||||
// Group examples by plan type
|
||||
let trial_examples = vec![
|
||||
single_example(
|
||||
"Trial - New User",
|
||||
div()
|
||||
.size_full()
|
||||
.child(new_trial_user.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Trial - Approaching Limit",
|
||||
div()
|
||||
.size_full()
|
||||
.child(trial_user_warning.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Trial - Request Limit Reached",
|
||||
div()
|
||||
.size_full()
|
||||
.child(trial_user_capped.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
];
|
||||
|
||||
let free_examples = vec![
|
||||
single_example(
|
||||
"Free - Normal Usage",
|
||||
div()
|
||||
.size_full()
|
||||
.child(free_user.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Free - Approaching Limit",
|
||||
div()
|
||||
.size_full()
|
||||
.child(free_user_warning.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Free - Request Limit Reached",
|
||||
div()
|
||||
.size_full()
|
||||
.child(free_user_capped.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
];
|
||||
|
||||
let paid_examples = vec![
|
||||
single_example(
|
||||
"Pro - Normal Usage",
|
||||
div()
|
||||
.size_full()
|
||||
.child(paid_user.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Pro - Approaching Limit",
|
||||
div()
|
||||
.size_full()
|
||||
.child(paid_user_warning.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Pro - Request Limit Reached",
|
||||
div()
|
||||
.size_full()
|
||||
.child(paid_user_capped.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
];
|
||||
|
||||
let paid_usage_based_examples = vec![
|
||||
single_example(
|
||||
"Pro with UBP - After Request Cap",
|
||||
div()
|
||||
.size_full()
|
||||
.child(paid_user_usage_based.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Pro with UBP - Approaching Spend Cap",
|
||||
div()
|
||||
.size_full()
|
||||
.child(paid_user_usage_based_warning.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Pro with UBP - Spend Cap Reached",
|
||||
div()
|
||||
.size_full()
|
||||
.child(paid_user_usage_based_capped.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
];
|
||||
|
||||
// Combine all examples
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.children(vec![
|
||||
Label::new("Trial Plan")
|
||||
.size(LabelSize::Large)
|
||||
.into_any_element(),
|
||||
example_group(trial_examples).vertical().into_any_element(),
|
||||
Label::new("Free Plan")
|
||||
.size(LabelSize::Large)
|
||||
.into_any_element(),
|
||||
example_group(free_examples).vertical().into_any_element(),
|
||||
Label::new("Pro Plan")
|
||||
.size(LabelSize::Large)
|
||||
.into_any_element(),
|
||||
example_group(paid_examples).vertical().into_any_element(),
|
||||
Label::new("Pro Plan with Usage-Based Pricing")
|
||||
.size(LabelSize::Large)
|
||||
.into_any_element(),
|
||||
example_group(paid_usage_based_examples)
|
||||
.vertical()
|
||||
.into_any_element(),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
8
crates/ui_parking_lot/src/ui_parking_lot.rs
Normal file
8
crates/ui_parking_lot/src/ui_parking_lot.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! # UI Parking Lot
|
||||
//!
|
||||
//! A place for engineering-ready components to be parked
|
||||
//! until someone has the time to pick them up and implement them further.
|
||||
|
||||
mod agent;
|
||||
|
||||
pub use agent::usage_banner::UsageBanner;
|
||||
@@ -113,7 +113,6 @@ use crate::persistence::{
|
||||
SerializedAxis,
|
||||
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
|
||||
};
|
||||
|
||||
pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
|
||||
|
||||
static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {
|
||||
|
||||
Reference in New Issue
Block a user