Compare commits
16 Commits
ex-bazel-g
...
alert-dial
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5caf6eb041 | ||
|
|
879f850f4f | ||
|
|
e6e72f9f2d | ||
|
|
30d91429e7 | ||
|
|
e255389cbe | ||
|
|
eeba056b84 | ||
|
|
f2338025b7 | ||
|
|
e29f4e6632 | ||
|
|
dd6f0dfc04 | ||
|
|
a84eb44564 | ||
|
|
bd1e57bd4a | ||
|
|
cd811a2014 | ||
|
|
74a5f6a464 | ||
|
|
c916f5d476 | ||
|
|
43ddcdef84 | ||
|
|
be6d13b6d8 |
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -108,6 +108,19 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alert_dialog"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"menu",
|
||||
"settings",
|
||||
"story",
|
||||
"theme",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aliasable"
|
||||
version = "0.1.3"
|
||||
@@ -10864,6 +10877,7 @@ dependencies = [
|
||||
name = "storybook"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alert_dialog",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"collab_ui",
|
||||
@@ -11728,6 +11742,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
name = "title_bar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alert_dialog",
|
||||
"auto_update",
|
||||
"call",
|
||||
"client",
|
||||
|
||||
@@ -120,6 +120,7 @@ members = [
|
||||
"crates/time_format",
|
||||
"crates/title_bar",
|
||||
"crates/ui",
|
||||
"crates/alert_dialog",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
"crates/util",
|
||||
@@ -298,6 +299,7 @@ theme_selector = { path = "crates/theme_selector" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
title_bar = { path = "crates/title_bar" }
|
||||
ui = { path = "crates/ui" }
|
||||
alert_dialog = { path = "crates/alert_dialog" }
|
||||
ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
util = { path = "crates/util" }
|
||||
|
||||
25
crates/alert_dialog/Cargo.toml
Normal file
25
crates/alert_dialog/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "alert_dialog"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/alert_dialog.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
settings.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
stories = ["dep:story"]
|
||||
420
crates/alert_dialog/src/alert_dialog.rs
Normal file
420
crates/alert_dialog/src/alert_dialog.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
#![deny(missing_docs)]
|
||||
//! Provides the Alert Dialog UI component – A modal dialog that interrupts the user's workflow to convey critical information.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{
|
||||
Action, AppContext, ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, View,
|
||||
WeakView,
|
||||
};
|
||||
use ui::{
|
||||
div, px, v_flex, vh, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, ElementId,
|
||||
ElevationIndex, FluentBuilder, Headline, HeadlineSize, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, RenderOnce, SharedString, Spacing, Styled, StyledTypography,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
#[derive(Clone, IntoElement)]
|
||||
struct AlertDialogButton {
|
||||
id: ElementId,
|
||||
label: SharedString,
|
||||
on_click: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
}
|
||||
|
||||
impl AlertDialogButton {
|
||||
fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label: label.into(),
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.on_click = Some(Arc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for AlertDialogButton {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
Button::new(self.id, self.label)
|
||||
.size(ButtonSize::Large)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.when_some(self.on_click, |this, on_click| {
|
||||
this.on_click(move |event, cx| {
|
||||
on_click(event, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_DIALOG_WIDTH: f32 = 440.0;
|
||||
const MIN_DIALOG_WIDTH: f32 = 260.0;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
enum AlertDialogLayout {
|
||||
/// For dialogs short titles and action names.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// Title: "Discard changes?"
|
||||
///
|
||||
/// Actions: "Cancel" | "Discard"
|
||||
#[default]
|
||||
Vertical,
|
||||
/// For dialogs with long titles or action names,
|
||||
/// or large amounts of content.
|
||||
///
|
||||
/// As titles, action names or content get longer, the dialog
|
||||
/// automatically switches to this layout
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
/// An alert dialog that interrupts the user's workflow to convey critical information.
|
||||
///
|
||||
/// Use this component when immediate user attention or action is required.
|
||||
///
|
||||
/// It blocks all other interactions until the user responds, making it suitable
|
||||
/// for important confirmations or critical error messages.
|
||||
#[derive(Clone)]
|
||||
pub struct AlertDialog {
|
||||
/// The title of the alert dialog
|
||||
pub title: SharedString,
|
||||
|
||||
/// The main message or content of the alert
|
||||
pub message: Option<SharedString>,
|
||||
|
||||
/// The primary action the user can take
|
||||
primary_action: AlertDialogButton,
|
||||
|
||||
/// The secondary action the user can take
|
||||
secondary_action: AlertDialogButton,
|
||||
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for AlertDialog {}
|
||||
impl FocusableView for AlertDialog {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
impl ModalView for AlertDialog {
|
||||
fn fade_out_background(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
impl AlertDialog {
|
||||
/// Create a new alert dialog
|
||||
pub fn new(
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
|
||||
) -> View<Self> {
|
||||
cx.new_view(|cx| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
f(
|
||||
Self {
|
||||
title: "Untitled Alert".into(),
|
||||
message: None,
|
||||
primary_action: AlertDialogButton::new("primary-action", "OK"),
|
||||
secondary_action: AlertDialogButton::new("secondary-action", "Cancel"),
|
||||
focus_handle,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the title of the alert dialog
|
||||
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
|
||||
self.title = title.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the main message or content of the alert dialog
|
||||
pub fn message(mut self, message: impl Into<SharedString>) -> Self {
|
||||
self.message = Some(message.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the primary action the user can take
|
||||
pub fn primary_action(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.primary_action = AlertDialogButton::new("primary-action", label).on_click(handler);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the secondary action the user can take
|
||||
pub fn secondary_action(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.secondary_action = AlertDialogButton::new("secondary-action", label).on_click(handler);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the secondary action to a dismiss action with a custom label
|
||||
///
|
||||
/// Example: "Close", "Dismiss", "No"
|
||||
pub fn secondary_dismiss_action(mut self, label: impl Into<SharedString>) -> Self {
|
||||
self.secondary_action = AlertDialogButton::new("secondary-action", label)
|
||||
.on_click(|_, cx| cx.dispatch_action(menu::Cancel.boxed_clone()));
|
||||
self
|
||||
}
|
||||
|
||||
fn dialog_layout(&self) -> AlertDialogLayout {
|
||||
let title_len = self.title.len();
|
||||
let primary_action_len = self.primary_action.label.len();
|
||||
let secondary_action_len = self.secondary_action.label.len();
|
||||
let message_len = self.message.as_ref().map_or(0, |m| m.len());
|
||||
|
||||
if title_len > 35
|
||||
|| primary_action_len > 14
|
||||
|| secondary_action_len > 14
|
||||
|| message_len > 80
|
||||
{
|
||||
AlertDialogLayout::Horizontal
|
||||
} else {
|
||||
AlertDialogLayout::Vertical
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns the alert dialog in a new modal
|
||||
pub fn show(&self, workspace: WeakView<Workspace>, cx: &mut ViewContext<Self>) {
|
||||
let this = self.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |_cx| this);
|
||||
cx.focus(&focus_handle);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AlertDialog {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let layout = self.dialog_layout();
|
||||
let spacing = if layout == AlertDialogLayout::Horizontal {
|
||||
Spacing::Large4X.rems(cx)
|
||||
} else {
|
||||
Spacing::XLarge.rems(cx)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.key_context("Alert")
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.occlude()
|
||||
.min_w(px(MIN_DIALOG_WIDTH))
|
||||
.max_w(if layout == AlertDialogLayout::Horizontal {
|
||||
px(MAX_DIALOG_WIDTH)
|
||||
} else {
|
||||
px(MIN_DIALOG_WIDTH)
|
||||
})
|
||||
.max_h(vh(0.75, cx))
|
||||
.flex_none()
|
||||
.overflow_hidden()
|
||||
.p(spacing)
|
||||
.gap(spacing)
|
||||
.rounded_lg()
|
||||
.font_ui(cx)
|
||||
.bg(ElevationIndex::ModalSurface.bg(cx))
|
||||
.shadow(ElevationIndex::ModalSurface.shadow())
|
||||
// Title and message
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
// This is a flex hack. Layout breaks without it ¯\_(ツ)_/¯
|
||||
.min_h(px(1.))
|
||||
.max_w_full()
|
||||
.flex_grow()
|
||||
.when(layout == AlertDialogLayout::Vertical, |this| {
|
||||
// If we had `.text_center()` we would use it here instead of centering the content
|
||||
// since this approach will only work as long as the content is a single line
|
||||
this.justify_center().mx_auto()
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
// Same as above, if `.text_center()` is supported in the future, use here.
|
||||
.when(layout == AlertDialogLayout::Vertical, |this| this.mx_auto())
|
||||
.child(Headline::new(self.title.clone()).size(HeadlineSize::Small)),
|
||||
)
|
||||
.when_some(self.message.clone(), |this, message| {
|
||||
// TODO: When content will be long (example: a document, log or stack trace)
|
||||
// we should render some sort of styled container, as well as allow the content to scroll
|
||||
this.child(
|
||||
div()
|
||||
// Same as above, if `.text_center()` is supported in the future, use here.
|
||||
.when(layout == AlertDialogLayout::Vertical, |this| this.mx_auto())
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.text_ui(cx)
|
||||
.child(message.clone()),
|
||||
)
|
||||
}),
|
||||
)
|
||||
// Actions & checkbox
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.w_full()
|
||||
.items_center()
|
||||
// Force buttons to stack for Horizontal layout
|
||||
.when(layout == AlertDialogLayout::Vertical, |this| {
|
||||
this.flex_col()
|
||||
})
|
||||
.when(layout == AlertDialogLayout::Horizontal, |this| {
|
||||
this.justify_between()
|
||||
.h(ButtonSize::Large.rems())
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
})
|
||||
.child(div().flex_shrink_0())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
.when(layout == AlertDialogLayout::Vertical, |this| {
|
||||
this.flex_col_reverse().w_full()
|
||||
})
|
||||
.when(layout == AlertDialogLayout::Horizontal, |this| {
|
||||
this.items_center()
|
||||
})
|
||||
.child(self.secondary_action.clone())
|
||||
.child(self.primary_action.clone()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Example stories for [AlertDialog]
|
||||
///
|
||||
/// Run with `script/storybook alert_dialog`
|
||||
#[cfg(feature = "stories")]
|
||||
pub mod alert_dialog_stories {
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use gpui::{Render, View};
|
||||
use story::{Story, StorySection};
|
||||
use ui::{prelude::*, ElevationIndex};
|
||||
|
||||
use super::AlertDialog;
|
||||
|
||||
pub struct AlertDialogStory {
|
||||
vertical_alert_dialog: View<AlertDialog>,
|
||||
horizontal_alert_dialog: View<AlertDialog>,
|
||||
long_content_alert_dialog: View<AlertDialog>,
|
||||
}
|
||||
|
||||
impl AlertDialogStory {
|
||||
pub fn new(cx: &mut WindowContext) -> Self {
|
||||
let vertical_alert_dialog = AlertDialog::new(cx, |dialog, _cx| {
|
||||
dialog
|
||||
.title("Discard changes?")
|
||||
.message("Something bad could happen...")
|
||||
.primary_action("Discard", |_, _| {
|
||||
println!("Discarded!");
|
||||
})
|
||||
.secondary_action("Cancel", |_, _| {
|
||||
println!("Cancelled!");
|
||||
})
|
||||
});
|
||||
|
||||
let horizontal_alert_dialog = AlertDialog::new(cx, |dialog, _cx| {
|
||||
dialog
|
||||
.title("Do you want to leave the current call?")
|
||||
.message("The current window will be closed, and connections to any shared projects will be terminated.")
|
||||
.primary_action("Leave Call", |_, _| {})
|
||||
.secondary_action("Cancel", |_, _| {})
|
||||
});
|
||||
|
||||
let long_content = r#"{
|
||||
"error": "RuntimeError",
|
||||
"message": "An unexpected error occurred during execution",
|
||||
"stackTrace": [
|
||||
{
|
||||
"fileName": "main.rs",
|
||||
"lineNumber": 42,
|
||||
"functionName": "process_data"
|
||||
},
|
||||
{
|
||||
"fileName": "utils.rs",
|
||||
"lineNumber": 23,
|
||||
"functionName": "validate_input"
|
||||
},
|
||||
{
|
||||
"fileName": "core.rs",
|
||||
"lineNumber": 105,
|
||||
"functionName": "execute_operation"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
|
||||
let long_content_alert_dialog = AlertDialog::new(cx, |dialog, _cx| {
|
||||
dialog
|
||||
.title("A RuntimeError occurred")
|
||||
.message(long_content)
|
||||
.primary_action("Send Report", |_, _| {})
|
||||
.secondary_action("Close", |_, _| {})
|
||||
});
|
||||
|
||||
Self {
|
||||
vertical_alert_dialog,
|
||||
horizontal_alert_dialog,
|
||||
long_content_alert_dialog,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AlertDialogStory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Story::container().child(
|
||||
StorySection::new()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.w(px(780.))
|
||||
.h(px(380.))
|
||||
.overflow_hidden()
|
||||
.bg(ElevationIndex::Background.bg(cx))
|
||||
.child(self.vertical_alert_dialog.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.w(px(580.))
|
||||
.h(px(420.))
|
||||
.overflow_hidden()
|
||||
.bg(ElevationIndex::Background.bg(cx))
|
||||
.child(self.horizontal_alert_dialog.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.w(px(580.))
|
||||
.h(px(780.))
|
||||
.overflow_hidden()
|
||||
.bg(ElevationIndex::Background.bg(cx))
|
||||
.child(self.long_content_alert_dialog.clone()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ strum = { workspace = true, features = ["derive"] }
|
||||
theme.workspace = true
|
||||
title_bar = { workspace = true, features = ["stories"] }
|
||||
ui = { workspace = true, features = ["stories"] }
|
||||
alert_dialog = { workspace = true, features = ["stories"] }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -12,6 +12,7 @@ use ui::prelude::*;
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ComponentStory {
|
||||
AlertDialog,
|
||||
ApplicationMenu,
|
||||
AutoHeightEditor,
|
||||
Avatar,
|
||||
@@ -46,6 +47,9 @@ pub enum ComponentStory {
|
||||
impl ComponentStory {
|
||||
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
|
||||
match self {
|
||||
Self::AlertDialog => cx
|
||||
.new_view(|cx| alert_dialog::alert_dialog_stories::AlertDialogStory::new(cx))
|
||||
.into(),
|
||||
Self::ApplicationMenu => cx
|
||||
.new_view(|cx| title_bar::ApplicationMenuStory::new(cx))
|
||||
.into(),
|
||||
|
||||
@@ -29,6 +29,7 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
alert_dialog.workspace = true
|
||||
auto_update.workspace = true
|
||||
call.workspace = true
|
||||
client.workspace = true
|
||||
|
||||
@@ -8,6 +8,7 @@ mod stories;
|
||||
|
||||
use crate::application_menu::ApplicationMenu;
|
||||
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||
use alert_dialog::AlertDialog;
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::ActiveCall;
|
||||
use client::{Client, UserStore};
|
||||
@@ -67,6 +68,7 @@ pub struct TitleBar {
|
||||
should_move: bool,
|
||||
application_menu: Option<View<ApplicationMenu>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
test_dialog: View<AlertDialog>,
|
||||
}
|
||||
|
||||
impl Render for TitleBar {
|
||||
@@ -84,6 +86,8 @@ impl Render for TitleBar {
|
||||
} else {
|
||||
cx.theme().colors().title_bar_background
|
||||
};
|
||||
let workspace = self.workspace.clone();
|
||||
let test_dialog = self.test_dialog.clone();
|
||||
|
||||
h_flex()
|
||||
.id("titlebar")
|
||||
@@ -139,6 +143,13 @@ impl Render for TitleBar {
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()),
|
||||
)
|
||||
.child(self.render_collaborator_list(cx))
|
||||
.child(
|
||||
Button::new("test-dialog", "Test Dialog").on_click(move |_, cx| {
|
||||
let workspace = workspace.clone();
|
||||
|
||||
test_dialog.update(cx, move |dialog, cx| dialog.show(workspace, cx));
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
@@ -220,6 +231,14 @@ impl TitleBar {
|
||||
}
|
||||
};
|
||||
|
||||
let test_dialog = AlertDialog::new(cx, |dialog, _| {
|
||||
dialog
|
||||
.title("Do you want to leave the current call?")
|
||||
.message("The current window will be closed, and connections to any shared projects will be terminated.")
|
||||
.primary_action("Leave Call", |_, _| {})
|
||||
.secondary_dismiss_action("Cancel")
|
||||
});
|
||||
|
||||
let mut subscriptions = Vec::new();
|
||||
subscriptions.push(
|
||||
cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
|
||||
@@ -242,6 +261,7 @@ impl TitleBar {
|
||||
user_store,
|
||||
client,
|
||||
_subscriptions: subscriptions,
|
||||
test_dialog,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use gpui::{hsla, point, px, BoxShadow};
|
||||
use gpui::{hsla, point, px, BoxShadow, Hsla, WindowContext};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use theme::{color_alpha, ActiveTheme};
|
||||
|
||||
/// Today, elevation is primarily used to add shadows to elements, and set the correct background for elements like buttons.
|
||||
///
|
||||
@@ -62,4 +63,18 @@ impl ElevationIndex {
|
||||
_ => smallvec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an appropriate background color for the given elevation index.
|
||||
pub fn bg(self, cx: &WindowContext) -> Hsla {
|
||||
match self {
|
||||
ElevationIndex::Background => cx.theme().colors().background,
|
||||
ElevationIndex::Surface => cx.theme().colors().surface_background,
|
||||
ElevationIndex::ElevatedSurface => cx.theme().colors().elevated_surface_background,
|
||||
ElevationIndex::Wash => hsla(0., 0., 0., 0.3),
|
||||
ElevationIndex::ModalSurface => cx.theme().colors().elevated_surface_background,
|
||||
ElevationIndex::DraggedElement => {
|
||||
color_alpha(cx.theme().colors().elevated_surface_background, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ pub enum Spacing {
|
||||
///
|
||||
/// Relative to the user's `ui_font_size` and [UiDensity] setting.
|
||||
XXLarge,
|
||||
/// 3X Large spacing - @16px/rem: `16px`|`20px`|`24px`
|
||||
///
|
||||
/// Relative to the user's `ui_font_size` and [UiDensity] setting.
|
||||
Large3X,
|
||||
/// 4X Large spacing - @16px/rem: `20px`|`24px`|`28px`
|
||||
///
|
||||
/// Relative to the user's `ui_font_size` and [UiDensity] setting.
|
||||
Large4X,
|
||||
}
|
||||
|
||||
impl Spacing {
|
||||
@@ -55,6 +63,8 @@ impl Spacing {
|
||||
Spacing::Large => 4. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::XLarge => 8. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::XXLarge => 12. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::Large3X => 16. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::Large4X => 20. / BASE_REM_SIZE_IN_PX,
|
||||
},
|
||||
UiDensity::Default => match self {
|
||||
Spacing::None => 0.,
|
||||
@@ -65,6 +75,8 @@ impl Spacing {
|
||||
Spacing::Large => 8. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::XLarge => 12. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::XXLarge => 16. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::Large3X => 20. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::Large4X => 24. / BASE_REM_SIZE_IN_PX,
|
||||
},
|
||||
UiDensity::Comfortable => match self {
|
||||
Spacing::None => 0.,
|
||||
@@ -75,6 +87,8 @@ impl Spacing {
|
||||
Spacing::Large => 10. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::XLarge => 16. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::XXLarge => 20. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::Large3X => 24. / BASE_REM_SIZE_IN_PX,
|
||||
Spacing::Large4X => 28. / BASE_REM_SIZE_IN_PX,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +174,8 @@ impl HeadlineSize {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Just added these to get stared with headlines but these
|
||||
// but we should have a per-headline size line height.
|
||||
/// Returns the line height for the headline size.
|
||||
pub fn line_height(self) -> Rems {
|
||||
match self {
|
||||
@@ -200,6 +202,7 @@ impl RenderOnce for Headline {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
|
||||
|
||||
div()
|
||||
.flex_none()
|
||||
.font(ui_font)
|
||||
.line_height(self.size.line_height())
|
||||
.text_size(self.size.rems())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// A trait for elements that can be selected.
|
||||
///
|
||||
/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status.
|
||||
@@ -30,6 +32,16 @@ impl Selection {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Selection {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unselected => write!(f, "Unselected"),
|
||||
Self::Indeterminate => write!(f, "Indeterminate"),
|
||||
Self::Selected => write!(f, "Selected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Selection {
|
||||
fn from(selected: bool) -> Self {
|
||||
if selected {
|
||||
@@ -49,3 +61,12 @@ impl From<Option<bool>> for Selection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<bool> for Selection {
|
||||
fn into(self) -> bool {
|
||||
match self {
|
||||
Self::Selected => true,
|
||||
Self::Unselected | Self::Indeterminate => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user