Compare commits
30 commits
main
...
onboarding
Author | SHA1 | Date | |
---|---|---|---|
![]() |
94102e962c | ||
![]() |
adb2e95e2a | ||
![]() |
7e22e57e08 | ||
![]() |
d30664d4b1 | ||
![]() |
5b2874b01a | ||
![]() |
ffb557c2d9 | ||
![]() |
6e6e6d86c3 | ||
![]() |
e32e1baf5f | ||
![]() |
bdabdbbf91 | ||
![]() |
fe9ef209a8 | ||
![]() |
b31af62d42 | ||
![]() |
8fde323dd0 | ||
![]() |
bff1f1c78a | ||
![]() |
66b36b772f | ||
![]() |
3c5d78800b | ||
![]() |
094c2bc32a | ||
![]() |
16cfdf7b94 | ||
![]() |
ff0345bb4f | ||
![]() |
124ebb872a | ||
![]() |
530b24258f | ||
![]() |
8c446ecd59 | ||
![]() |
f6407fdff1 | ||
![]() |
db52799129 | ||
![]() |
db575d8a04 | ||
![]() |
63dc4925f6 | ||
![]() |
3dfaac0b4d | ||
![]() |
4f52cece69 | ||
![]() |
02f54e5958 | ||
![]() |
435dad71fb | ||
![]() |
ea74f4784a |
33 changed files with 2561 additions and 151 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -10814,6 +10814,35 @@ dependencies = [
|
|||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onboarding_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"command_palette_hooks",
|
||||
"component",
|
||||
"db",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"project",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"settings_ui",
|
||||
"smallvec",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"vim_mode_setting",
|
||||
"welcome",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
@ -19972,6 +20001,7 @@ dependencies = [
|
|||
"collab_ui",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component",
|
||||
"copilot",
|
||||
"dap",
|
||||
|
@ -20022,6 +20052,7 @@ dependencies = [
|
|||
"nix 0.29.0",
|
||||
"node_runtime",
|
||||
"notifications",
|
||||
"onboarding_ui",
|
||||
"outline",
|
||||
"outline_panel",
|
||||
"parking_lot",
|
||||
|
|
|
@ -102,6 +102,7 @@ members = [
|
|||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
"crates/ollama",
|
||||
"crates/onboarding_ui",
|
||||
"crates/open_ai",
|
||||
"crates/open_router",
|
||||
"crates/outline",
|
||||
|
@ -314,6 +315,7 @@ multi_buffer = { path = "crates/multi_buffer" }
|
|||
node_runtime = { path = "crates/node_runtime" }
|
||||
notifications = { path = "crates/notifications" }
|
||||
ollama = { path = "crates/ollama" }
|
||||
onboarding_ui = { path = "crates/onboarding_ui" }
|
||||
open_ai = { path = "crates/open_ai" }
|
||||
open_router = { path = "crates/open_router", features = ["schemars"] }
|
||||
outline = { path = "crates/outline" }
|
||||
|
|
BIN
assets/fonts/plex-sans/ZedPlexSans-Medium.ttf
Normal file
BIN
assets/fonts/plex-sans/ZedPlexSans-Medium.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/plex-sans/ZedPlexSans-MediumItalic.ttf
Normal file
BIN
assets/fonts/plex-sans/ZedPlexSans-MediumItalic.ttf
Normal file
Binary file not shown.
19
assets/images/atom_logo.svg
Normal file
19
assets/images/atom_logo.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_460_1758)" filter="url(#filter0_d_460_1758)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.53471 10.9653C6.40877 11.2268 6.29908 11.4479 6.19486 11.6718C5.6733 12.7932 5.24127 13.9457 5.0183 15.1667C4.89424 15.8457 4.82642 16.5298 5.00377 17.2115C5.15611 17.7973 5.47314 18.0981 6.01549 18.1229C6.25283 18.134 6.50111 18.0837 6.73221 18.0186C7.34408 17.8453 7.87643 17.5106 8.39127 17.1481C8.51627 17.06 8.64408 17.005 8.80002 17.0629C9.07627 17.1657 9.17627 17.532 8.9433 17.7059C8.10252 18.3323 7.21267 18.8725 6.12314 18.9059C5.2308 18.9334 4.58486 18.4548 4.30017 17.6042C4.08767 16.9695 4.07517 16.3167 4.15314 15.6617C4.38471 13.712 5.13721 11.9406 6.0408 10.2234C6.11846 10.0759 6.12314 9.95871 6.06814 9.80106C5.84252 9.15309 5.63267 8.49949 5.42658 7.84496C5.38752 7.7209 5.34752 7.68559 5.21283 7.71512C4.32752 7.90981 3.45533 8.14746 2.67002 8.61262C2.36674 8.79231 2.0758 9.01543 1.8333 9.27012C1.43002 9.69356 1.42236 10.1622 1.75189 10.6456C2.03939 11.0676 2.44455 11.3562 2.87205 11.617C3.0058 11.6989 3.14689 11.7686 3.28158 11.8489C3.47736 11.9659 3.54439 12.1928 3.44392 12.3815C3.34314 12.5704 3.11705 12.6453 2.91174 12.5396C2.31111 12.2309 1.75096 11.8645 1.30174 11.3504C1.02642 11.0354 0.824706 10.6814 0.757987 10.2617C0.674862 9.73825 0.824862 9.27699 1.13924 8.86262C1.46346 8.43559 1.88924 8.1309 2.35408 7.87778C3.17846 7.42887 4.06674 7.16075 4.97924 6.96309C5.04174 6.94949 5.10361 6.93262 5.1883 6.91153C5.14533 6.66809 5.09924 6.43184 5.06236 6.19387C4.91283 5.22606 4.84392 4.25746 5.08158 3.29278C5.17564 2.91121 5.3233 2.55059 5.58017 2.24668C6.10455 1.62684 6.77892 1.48887 7.53518 1.67356C8.28268 1.85621 8.91033 2.27012 9.49564 2.75184C9.66127 2.88809 9.6658 3.13418 9.53064 3.29278C9.39268 3.45449 9.17814 3.47559 8.99033 3.34762C8.69533 3.14684 8.40846 2.93231 8.10189 2.7509C7.75064 2.54324 7.37346 2.3959 6.95314 2.39387C6.60596 2.39215 6.33611 2.5334 6.1383 2.81465C5.88767 3.17137 5.79814 3.58293 5.7508 4.00496C5.65064 4.89762 5.76955 5.77512 5.94346 6.6484C5.96205 6.74121 5.99767 6.77403 6.09533 6.76418C6.80002 6.69387 7.50486 6.62371 8.21064 6.56481C8.36408 6.55215 8.46424 6.49668 8.55939 6.37637C9.47658 5.21778 10.482 4.14496 11.6649 3.24981C12.3238 2.75121 13.02 2.31653 13.8211 2.07918C14.323 1.93059 14.831 1.87559 15.3364 2.05324C15.9677 2.27512 16.3205 2.75481 16.5024 3.37387C16.6952 4.03106 16.665 4.70106 16.5867 5.3709C16.5653 5.5425 16.5378 5.71329 16.5042 5.88293C16.4603 6.11387 16.2891 6.23621 16.0585 6.20934C15.8488 6.18496 15.7119 6.01637 15.7339 5.78231C15.7644 5.45949 15.838 5.1384 15.8413 4.81621C15.8452 4.43496 15.837 4.04387 15.7577 3.67356C15.5922 2.90356 15.0595 2.60153 14.2716 2.77528C13.6805 2.90543 13.1563 3.18356 12.6572 3.51246C11.632 4.18809 10.7569 5.03293 9.94439 5.94637C9.7933 6.11621 9.64799 6.29137 9.50111 6.46481C9.49221 6.47512 9.49283 6.49356 9.48158 6.53746H9.69424C11.7866 6.55856 13.8481 6.80731 15.8628 7.38715C16.8222 7.6634 17.7483 8.0234 18.6005 8.55356C19.0539 8.83559 19.4702 9.16199 19.7994 9.58793C20.4708 10.4567 20.4144 11.432 19.642 12.21C19.1205 12.7354 18.4781 13.0673 17.7939 13.3251C17.6003 13.3978 17.3872 13.2911 17.3131 13.1056C17.2317 12.9018 17.32 12.6911 17.5272 12.5906C17.8431 12.4375 18.1717 12.3048 18.4705 12.1229C18.7201 11.9716 18.9485 11.7878 19.1497 11.5762C19.568 11.1311 19.5835 10.6445 19.2288 10.1464C18.9631 9.77324 18.6063 9.50043 18.2225 9.26199C17.2756 8.67356 16.2366 8.30903 15.1667 8.0245C14.0514 7.72778 12.9183 7.52074 11.7675 7.43887C10.8505 7.37371 9.93017 7.35496 9.01143 7.31137C8.87877 7.30496 8.78814 7.34418 8.71439 7.45793C8.1758 8.28856 7.63111 9.11528 7.09533 9.94778C7.05627 10.0084 7.03971 10.1175 7.06643 10.1817C7.97346 12.3401 9.12221 14.3548 10.6446 16.1434C11.2855 16.8961 11.9825 17.5922 12.8289 18.1189C13.2261 18.3659 13.6406 18.572 14.1214 18.604C14.557 18.6334 14.8749 18.4511 15.0911 18.0814C15.2949 17.7331 15.3766 17.3478 15.4131 16.9518C15.5313 15.6742 15.2786 14.4418 14.9436 13.2221C14.6005 11.9728 14.1149 10.7762 13.5585 9.60715C13.5136 9.51309 13.4769 9.40137 13.4817 9.30012C13.4906 9.1159 13.636 8.98481 13.8177 8.95637C13.9774 8.93137 14.1455 9.01918 14.2253 9.19043C14.4244 9.61668 14.6264 10.0422 14.8088 10.4757C15.3716 11.815 15.8247 13.189 16.0641 14.6262C16.2311 15.6293 16.311 16.6361 16.0791 17.6409C16.0263 17.8671 15.951 18.0875 15.8542 18.2987C15.4594 19.1618 14.6588 19.5518 13.7311 19.349C12.9769 19.1839 12.3403 18.7898 11.7464 18.319C10.6541 17.4532 9.76783 16.3976 8.96627 15.2667C8.05252 13.9784 7.2808 12.6084 6.63424 11.1676C6.61002 11.114 6.58221 11.062 6.53471 10.9653ZM6.13971 7.53559L6.63908 9.12371C7.03361 8.52153 7.40142 7.96028 7.79705 7.35606L6.13971 7.53559ZM10.5731 11.6353C9.94549 11.6345 9.43471 11.1257 9.43596 10.5026C9.43705 9.86949 9.94721 9.36496 10.5853 9.36512C11.2125 9.36543 11.7222 9.87371 11.7227 10.4987C11.723 11.1311 11.2122 11.6361 10.5731 11.6353Z" fill="#AAAFBB"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_460_1758" x="0.5" y="0.5" width="20" height="21.25" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.25"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_460_1758"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_460_1758" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_460_1758">
|
||||
<rect width="20" height="20" fill="white" transform="translate(0.5 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 5.7 KiB |
|
@ -25,7 +25,11 @@
|
|||
// Features that can be globally enabled or disabled
|
||||
"features": {
|
||||
// Which edit prediction provider to use.
|
||||
"edit_prediction_provider": "zed"
|
||||
"edit_prediction_provider": "zed",
|
||||
// A globally enable or disable AI features.
|
||||
//
|
||||
// This setting supersedes all other settings related to AI features.
|
||||
"ai_assistance": true
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Plex Mono",
|
||||
|
|
|
@ -33,6 +33,7 @@ use gpui::{
|
|||
App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal,
|
||||
WeakEntity, Window, point,
|
||||
};
|
||||
use language::language_settings;
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use language_model::{
|
||||
ConfigurationError, ConfiguredModel, LanguageModelRegistry, report_assistant_event,
|
||||
|
@ -1768,7 +1769,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
if !AgentSettings::get_global(cx).enabled {
|
||||
if !AgentSettings::get_global(cx).enabled || !language_settings::ai_enabled(cx) {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
}
|
||||
|
||||
|
|
|
@ -405,6 +405,23 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the box shadow of the element.
|
||||
///
|
||||
/// A hairline shadow is a very thin shadow that is often used
|
||||
/// to create a subtle depth effect under an element.
|
||||
#visibility fn shadow_hairline(mut self) -> Self {
|
||||
use gpui::{BoxShadow, hsla, point, px};
|
||||
use std::vec;
|
||||
|
||||
self.style().box_shadow = Some(vec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.16),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(0.),
|
||||
spread_radius: px(0.),
|
||||
}]);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the box shadow of the element.
|
||||
/// [Docs](https://tailwindcss.com/docs/box-shadow)
|
||||
#visibility fn shadow_2xs(mut self) -> Self {
|
||||
|
|
|
@ -16,8 +16,10 @@ use serde::{
|
|||
de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor},
|
||||
};
|
||||
|
||||
use fs::Fs;
|
||||
use settings::{
|
||||
ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore,
|
||||
update_settings_file,
|
||||
};
|
||||
use shellexpand;
|
||||
use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc};
|
||||
|
@ -54,11 +56,18 @@ pub fn all_language_settings<'a>(
|
|||
AllLanguageSettings::get(location, cx)
|
||||
}
|
||||
|
||||
/// Returns whether AI assistance is globally enabled or disabled.
|
||||
pub fn ai_enabled(cx: &App) -> bool {
|
||||
all_language_settings(None, cx).ai_assistance
|
||||
}
|
||||
|
||||
/// The settings for all languages.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AllLanguageSettings {
|
||||
/// The edit prediction settings.
|
||||
pub edit_predictions: EditPredictionSettings,
|
||||
/// Whether AI assistance is enabled.
|
||||
pub ai_assistance: bool,
|
||||
pub defaults: LanguageSettings,
|
||||
languages: HashMap<LanguageName, LanguageSettings>,
|
||||
pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
|
||||
|
@ -646,6 +655,8 @@ pub struct CopilotSettingsContent {
|
|||
pub struct FeaturesContent {
|
||||
/// Determines which edit prediction provider to use.
|
||||
pub edit_prediction_provider: Option<EditPredictionProvider>,
|
||||
/// Whether AI assistance is enabled.
|
||||
pub ai_assistance: Option<bool>,
|
||||
}
|
||||
|
||||
/// Controls the soft-wrapping behavior in the editor.
|
||||
|
@ -1122,6 +1133,26 @@ impl AllLanguageSettings {
|
|||
pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
|
||||
self.edit_predictions.mode
|
||||
}
|
||||
|
||||
/// Returns whether AI assistance is enabled.
|
||||
pub fn is_ai_assistance_enabled(&self) -> bool {
|
||||
self.ai_assistance
|
||||
}
|
||||
|
||||
/// Sets AI assistance to the specified state and updates the settings file.
|
||||
pub fn set_ai_assistance(enabled: bool, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
let current_state = Self::get_global(cx).ai_assistance;
|
||||
|
||||
if current_state == enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
update_settings_file::<Self>(fs, cx, move |file, _| {
|
||||
file.features
|
||||
.get_or_insert(Default::default())
|
||||
.ai_assistance = Some(enabled);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
|
||||
|
@ -1247,6 +1278,12 @@ impl settings::Settings for AllLanguageSettings {
|
|||
.map(|settings| settings.enabled_in_text_threads)
|
||||
.unwrap_or(true);
|
||||
|
||||
let mut ai_assistance = default_value
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|f| f.ai_assistance)
|
||||
.unwrap_or(true);
|
||||
|
||||
let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
|
||||
|
||||
for (language, patterns) in &default_value.file_types {
|
||||
|
@ -1268,6 +1305,14 @@ impl settings::Settings for AllLanguageSettings {
|
|||
edit_prediction_provider = Some(provider);
|
||||
}
|
||||
|
||||
if let Some(user_ai_assistance) = user_settings
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|f| f.ai_assistance)
|
||||
{
|
||||
ai_assistance = user_ai_assistance;
|
||||
}
|
||||
|
||||
if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
|
||||
edit_predictions_mode = edit_predictions.mode;
|
||||
enabled_in_text_threads = edit_predictions.enabled_in_text_threads;
|
||||
|
@ -1359,6 +1404,7 @@ impl settings::Settings for AllLanguageSettings {
|
|||
copilot: copilot_settings,
|
||||
enabled_in_text_threads,
|
||||
},
|
||||
ai_assistance,
|
||||
defaults,
|
||||
languages,
|
||||
file_types,
|
||||
|
|
42
crates/onboarding_ui/Cargo.toml
Normal file
42
crates/onboarding_ui/Cargo.toml
Normal file
|
@ -0,0 +1,42 @@
|
|||
[package]
|
||||
name = "onboarding_ui"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/onboarding_ui.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
component.workspace = true
|
||||
db.workspace = true
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
project.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
settings_ui.workspace = true
|
||||
smallvec.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
welcome.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
86
crates/onboarding_ui/src/components/callout_row.rs
Normal file
86
crates/onboarding_ui/src/components/callout_row.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use component::{example_group_with_title, single_example};
|
||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
||||
use smallvec::SmallVec;
|
||||
use ui::{Label, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct CalloutRow {
|
||||
title: SharedString,
|
||||
lines: SmallVec<[SharedString; 4]>,
|
||||
}
|
||||
|
||||
impl CalloutRow {
|
||||
pub fn new(title: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
lines: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line(mut self, line: impl Into<SharedString>) -> Self {
|
||||
self.lines.push(line.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CalloutRow {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
div().px_2().child(
|
||||
v_flex()
|
||||
.p_3()
|
||||
.gap_1()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(Label::new(self.title).weight(gpui::FontWeight::MEDIUM))
|
||||
.children(
|
||||
self.lines
|
||||
.into_iter()
|
||||
.map(|line| Label::new(line).size(LabelSize::Small).color(Color::Muted)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CalloutRow {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"RowCallout"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let examples = example_group_with_title(
|
||||
"CalloutRow Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Privacy Notice",
|
||||
CalloutRow::new("We don't use your code to train AI models")
|
||||
.line("You choose which providers you enable, and they have their own privacy policies.")
|
||||
.line("Read more about our privacy practices in our Privacy Policy.")
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Single Line",
|
||||
CalloutRow::new("Important Notice")
|
||||
.line("This is a single line of information.")
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Multi Line",
|
||||
CalloutRow::new("Getting Started")
|
||||
.line("Welcome to Zed! Here are some things to know:")
|
||||
.line("• Use Cmd+P to quickly open files")
|
||||
.line("• Use Cmd+Shift+P to access the command palette")
|
||||
.line("• Check out the documentation for more tips")
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Some(v_flex().p_4().gap_4().child(examples).into_any_element())
|
||||
}
|
||||
}
|
134
crates/onboarding_ui/src/components/checkbox_row.rs
Normal file
134
crates/onboarding_ui/src/components/checkbox_row.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use component::{example_group_with_title, single_example};
|
||||
use gpui::StatefulInteractiveElement as _;
|
||||
use gpui::{AnyElement, App, ClickEvent, IntoElement, RenderOnce, Window};
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct CheckboxRow {
|
||||
label: SharedString,
|
||||
description: Option<SharedString>,
|
||||
checked: bool,
|
||||
on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl CheckboxRow {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
description: None,
|
||||
checked: false,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||
self.description = Some(description.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn checked(mut self, checked: bool) -> Self {
|
||||
self.checked = checked;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CheckboxRow {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let checked = self.checked;
|
||||
let on_click = self.on_click;
|
||||
|
||||
let checkbox = gpui::div()
|
||||
.w_4()
|
||||
.h_4()
|
||||
.rounded_sm()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.when(checked, |this| {
|
||||
this.bg(cx.theme().colors().element_selected)
|
||||
.border_color(cx.theme().colors().border_selected)
|
||||
})
|
||||
.hover(|this| this.bg(cx.theme().colors().element_hover))
|
||||
.child(gpui::div().when(checked, |this| {
|
||||
this.size_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(Icon::new(IconName::Check))
|
||||
}));
|
||||
|
||||
let main_row = if let Some(on_click) = on_click {
|
||||
gpui::div()
|
||||
.id("checkbox-row")
|
||||
.h_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(checkbox)
|
||||
.child(Label::new(self.label))
|
||||
.cursor_pointer()
|
||||
.on_click(move |_event, window, cx| on_click(window, cx))
|
||||
} else {
|
||||
gpui::div()
|
||||
.id("checkbox-row")
|
||||
.h_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(checkbox)
|
||||
.child(Label::new(self.label))
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.px_5()
|
||||
.py_1()
|
||||
.gap_1()
|
||||
.child(main_row)
|
||||
.when_some(self.description, |this, desc| {
|
||||
this.child(
|
||||
gpui::div()
|
||||
.ml_6()
|
||||
.child(Label::new(desc).size(LabelSize::Small).color(Color::Muted)),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CheckboxRow {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"RowCheckbox"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let examples = example_group_with_title(
|
||||
"CheckboxRow Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Unchecked",
|
||||
CheckboxRow::new("Enable Vim Mode").into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Checked",
|
||||
CheckboxRow::new("Send Crash Reports")
|
||||
.checked(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Description",
|
||||
CheckboxRow::new("Send Telemetry")
|
||||
.description("Help improve Zed by sending anonymous usage data")
|
||||
.checked(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Some(v_flex().p_4().gap_4().child(examples).into_any_element())
|
||||
}
|
||||
}
|
78
crates/onboarding_ui/src/components/header_row.rs
Normal file
78
crates/onboarding_ui/src/components/header_row.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use component::{example_group_with_title, single_example};
|
||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
||||
use ui::{Label, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct HeaderRow {
|
||||
label: SharedString,
|
||||
end_slot: Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl HeaderRow {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
end_slot: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_slot(mut self, slot: impl IntoElement) -> Self {
|
||||
self.end_slot = Some(slot.into_any_element());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for HeaderRow {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
h_flex()
|
||||
.h(px(32.))
|
||||
.w_full()
|
||||
.px_5()
|
||||
.justify_between()
|
||||
.child(Label::new(self.label))
|
||||
.when_some(self.end_slot, |this, slot| this.child(slot))
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for HeaderRow {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"RowHeader"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let examples = example_group_with_title(
|
||||
"HeaderRow Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Simple Header",
|
||||
HeaderRow::new("Pick a Theme").into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Header with Button",
|
||||
HeaderRow::new("Pick a Theme")
|
||||
.end_slot(
|
||||
Button::new("more_themes", "More Themes")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Header with Icon Button",
|
||||
HeaderRow::new("Settings")
|
||||
.end_slot(
|
||||
IconButton::new("refresh", IconName::RotateCw)
|
||||
.style(ButtonStyle::Subtle),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Some(v_flex().p_4().gap_4().child(examples).into_any_element())
|
||||
}
|
||||
}
|
11
crates/onboarding_ui/src/components/mod.rs
Normal file
11
crates/onboarding_ui/src/components/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
mod callout_row;
|
||||
mod checkbox_row;
|
||||
mod header_row;
|
||||
mod selectable_tile;
|
||||
mod selectable_tile_row;
|
||||
|
||||
pub use callout_row::CalloutRow;
|
||||
pub use checkbox_row::CheckboxRow;
|
||||
pub use header_row::HeaderRow;
|
||||
pub use selectable_tile::SelectableTile;
|
||||
pub use selectable_tile_row::SelectableTileRow;
|
165
crates/onboarding_ui/src/components/selectable_tile.rs
Normal file
165
crates/onboarding_ui/src/components/selectable_tile.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use component::{example_group_with_title, single_example};
|
||||
use gpui::{ClickEvent, transparent_black};
|
||||
use smallvec::SmallVec;
|
||||
use ui::{Vector, VectorName, prelude::*, utils::CornerSolver};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct SelectableTile {
|
||||
id: ElementId,
|
||||
width: DefiniteLength,
|
||||
height: DefiniteLength,
|
||||
parent_focused: bool,
|
||||
selected: bool,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl SelectableTile {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
width: impl Into<DefiniteLength>,
|
||||
height: impl Into<DefiniteLength>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
width: width.into(),
|
||||
height: height.into(),
|
||||
parent_focused: false,
|
||||
selected: false,
|
||||
children: SmallVec::new(),
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn w(mut self, width: impl Into<DefiniteLength>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn h(mut self, height: impl Into<DefiniteLength>) -> Self {
|
||||
self.height = height.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parent_focused(mut self, focused: bool) -> Self {
|
||||
self.parent_focused = focused;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SelectableTile {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let ring_corner_radius = px(8.);
|
||||
let ring_width = px(1.);
|
||||
let padding = px(2.);
|
||||
let content_border_width = px(0.);
|
||||
let content_border_radius = CornerSolver::child_radius(
|
||||
ring_corner_radius,
|
||||
ring_width,
|
||||
padding,
|
||||
content_border_width,
|
||||
);
|
||||
|
||||
let mut element = h_flex()
|
||||
.id(self.id)
|
||||
.w(self.width)
|
||||
.h(self.height)
|
||||
.overflow_hidden()
|
||||
.rounded(ring_corner_radius)
|
||||
.border(ring_width)
|
||||
.border_color(if self.selected && self.parent_focused {
|
||||
cx.theme().status().info
|
||||
} else if self.selected {
|
||||
cx.theme().colors().border
|
||||
} else {
|
||||
transparent_black()
|
||||
})
|
||||
.p(padding)
|
||||
.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.rounded(content_border_radius)
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.shadow_hairline()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.children(self.children),
|
||||
);
|
||||
|
||||
if let Some(on_click) = self.on_click {
|
||||
element = element.on_click(move |event, window, cx| {
|
||||
on_click(event, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for SelectableTile {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SelectableTile {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let states = example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
SelectableTile::new("default", px(40.), px(40.))
|
||||
.parent_focused(false)
|
||||
.selected(false)
|
||||
.child(div().p_4().child(Vector::new(
|
||||
VectorName::ZedLogo,
|
||||
rems(18. / 16.),
|
||||
rems(18. / 16.),
|
||||
)))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
SelectableTile::new("selected", px(40.), px(40.))
|
||||
.parent_focused(false)
|
||||
.selected(true)
|
||||
.child(div().p_4().child(Vector::new(
|
||||
VectorName::ZedLogo,
|
||||
rems(18. / 16.),
|
||||
rems(18. / 16.),
|
||||
)))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected & Parent Focused",
|
||||
SelectableTile::new("selected_focused", px(40.), px(40.))
|
||||
.parent_focused(true)
|
||||
.selected(true)
|
||||
.child(div().p_4().child(Vector::new(
|
||||
VectorName::ZedLogo,
|
||||
rems(18. / 16.),
|
||||
rems(18. / 16.),
|
||||
)))
|
||||
.into_any_element(),
|
||||
),
|
||||
]);
|
||||
|
||||
Some(v_flex().p_4().gap_4().child(states).into_any_element())
|
||||
}
|
||||
}
|
124
crates/onboarding_ui/src/components/selectable_tile_row.rs
Normal file
124
crates/onboarding_ui/src/components/selectable_tile_row.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use super::selectable_tile::SelectableTile;
|
||||
use component::{example_group_with_title, single_example};
|
||||
use gpui::{
|
||||
AnyElement, App, IntoElement, RenderOnce, StatefulInteractiveElement, Window, prelude::*,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use ui::{Label, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct SelectableTileRow {
|
||||
gap: Pixels,
|
||||
tiles: SmallVec<[SelectableTile; 8]>,
|
||||
}
|
||||
|
||||
impl SelectableTileRow {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
gap: px(12.),
|
||||
tiles: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
|
||||
self.gap = gap.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tile(mut self, tile: SelectableTile) -> Self {
|
||||
self.tiles.push(tile);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SelectableTileRow {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
h_flex().w_full().px_5().gap(self.gap).children(self.tiles)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SelectableTileRow {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"RowSelectableTile"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let examples = example_group_with_title(
|
||||
"SelectableTileRow Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Theme Tiles",
|
||||
SelectableTileRow::new()
|
||||
.gap(px(12.))
|
||||
.tile(
|
||||
SelectableTile::new("tile1", px(100.), px(80.))
|
||||
.selected(true)
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.bg(gpui::red())
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(Label::new("Dark")),
|
||||
),
|
||||
)
|
||||
.tile(
|
||||
SelectableTile::new("tile2", px(100.), px(80.)).child(
|
||||
div()
|
||||
.size_full()
|
||||
.bg(gpui::green())
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(Label::new("Light")),
|
||||
),
|
||||
)
|
||||
.tile(
|
||||
SelectableTile::new("tile3", px(100.), px(80.))
|
||||
.parent_focused(true)
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.bg(gpui::blue())
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(Label::new("Auto")),
|
||||
),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Icon Tiles",
|
||||
SelectableTileRow::new()
|
||||
.gap(px(8.))
|
||||
.tile(
|
||||
SelectableTile::new("icon1", px(48.), px(48.))
|
||||
.selected(true)
|
||||
.child(Icon::new(IconName::Code).size(IconSize::Medium)),
|
||||
)
|
||||
.tile(
|
||||
SelectableTile::new("icon2", px(48.), px(48.))
|
||||
.child(Icon::new(IconName::Terminal).size(IconSize::Medium)),
|
||||
)
|
||||
.tile(
|
||||
SelectableTile::new("icon3", px(48.), px(48.))
|
||||
.child(Icon::new(IconName::FileCode).size(IconSize::Medium)),
|
||||
)
|
||||
.tile(
|
||||
SelectableTile::new("icon4", px(48.), px(48.))
|
||||
.child(Icon::new(IconName::Settings).size(IconSize::Medium)),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Some(v_flex().p_4().gap_4().child(examples).into_any_element())
|
||||
}
|
||||
}
|
94
crates/onboarding_ui/src/juicy_button.rs
Normal file
94
crates/onboarding_ui/src/juicy_button.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use gpui::{FontWeight, *};
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct JuicyButton {
|
||||
base: Div,
|
||||
label: SharedString,
|
||||
keybinding: Option<AnyElement>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl JuicyButton {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
base: div(),
|
||||
label: label.into(),
|
||||
keybinding: None,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keybinding(mut self, keybinding: Option<impl IntoElement>) -> Self {
|
||||
if let Some(kb) = keybinding {
|
||||
self.keybinding = Some(kb.into_any_element());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Clickable for JuicyButton {
|
||||
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
fn cursor_style(mut self, style: gpui::CursorStyle) -> Self {
|
||||
self.base = self.base.cursor(style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for JuicyButton {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let mut children = vec![
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(14.))
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_color(cx.theme().colors().text)
|
||||
.child(self.label),
|
||||
)
|
||||
.into_any_element(),
|
||||
];
|
||||
|
||||
if let Some(keybinding) = self.keybinding {
|
||||
children.push(
|
||||
div()
|
||||
.flex_none()
|
||||
.bg(gpui::white().opacity(0.2))
|
||||
.rounded_md()
|
||||
.px_1()
|
||||
.child(keybinding)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
|
||||
self.base
|
||||
.id("juicy-button")
|
||||
.w_full()
|
||||
.h(rems(2.))
|
||||
.px(rems(1.5))
|
||||
.rounded(px(6.))
|
||||
.bg(cx.theme().colors().icon.opacity(0.12))
|
||||
.shadow_hairline()
|
||||
.hover(|style| {
|
||||
style.bg(cx.theme().colors().icon.opacity(0.12)) // Darker blue on hover
|
||||
})
|
||||
.active(|style| {
|
||||
style
|
||||
.bg(rgb(0x1e40af)) // Even darker on active
|
||||
.shadow_md()
|
||||
})
|
||||
.cursor_pointer()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.when_some(self.on_click, |div, on_click| div.on_click(on_click))
|
||||
.children(children)
|
||||
}
|
||||
}
|
1290
crates/onboarding_ui/src/onboarding_ui.rs
Normal file
1290
crates/onboarding_ui/src/onboarding_ui.rs
Normal file
File diff suppressed because it is too large
Load diff
53
crates/onboarding_ui/src/persistence.rs
Normal file
53
crates/onboarding_ui/src/persistence.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use anyhow::Result;
|
||||
use db::{define_connection, query, sqlez::statement::Statement, sqlez_macros::sql};
|
||||
|
||||
use workspace::{WorkspaceDb, WorkspaceId};
|
||||
|
||||
define_connection! {
|
||||
pub static ref ONBOARDING_DB: OnboardingDb<WorkspaceDb> =
|
||||
&[sql!(
|
||||
CREATE TABLE onboarding_state (
|
||||
workspace_id INTEGER,
|
||||
item_id INTEGER UNIQUE,
|
||||
current_page TEXT,
|
||||
completed_pages TEXT,
|
||||
PRIMARY KEY(workspace_id, item_id),
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||
ON DELETE CASCADE
|
||||
) STRICT;
|
||||
)];
|
||||
}
|
||||
|
||||
impl OnboardingDb {
|
||||
pub async fn save_state(
|
||||
&self,
|
||||
item_id: u64,
|
||||
workspace_id: WorkspaceId,
|
||||
current_page: String,
|
||||
completed_pages: String,
|
||||
) -> Result<()> {
|
||||
let query =
|
||||
"INSERT INTO onboarding_state(item_id, workspace_id, current_page, completed_pages)
|
||||
VALUES (?1, ?2, ?3, ?4)
|
||||
ON CONFLICT DO UPDATE SET
|
||||
current_page = ?3,
|
||||
completed_pages = ?4";
|
||||
self.write(move |conn| {
|
||||
let mut statement = Statement::prepare(conn, query)?;
|
||||
let mut next_index = statement.bind(&item_id, 1)?;
|
||||
next_index = statement.bind(&workspace_id, next_index)?;
|
||||
next_index = statement.bind(¤t_page, next_index)?;
|
||||
statement.bind(&completed_pages, next_index)?;
|
||||
statement.exec()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
query! {
|
||||
pub fn get_state(item_id: u64, workspace_id: WorkspaceId) -> Result<Option<(String, String)>> {
|
||||
SELECT current_page, completed_pages
|
||||
FROM onboarding_state
|
||||
WHERE item_id = ? AND workspace_id = ?
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,13 +2,11 @@
|
|||
use gpui::{Hsla, Length};
|
||||
use std::sync::Arc;
|
||||
use theme::{Theme, ThemeRegistry};
|
||||
use ui::{
|
||||
IntoElement, RenderOnce, component_prelude::Documented, prelude::*, utils::inner_corner_radius,
|
||||
};
|
||||
use ui::{IntoElement, RenderOnce, prelude::*, utils::CornerSolver};
|
||||
|
||||
/// Shows a preview of a theme as an abstract illustration
|
||||
/// of a thumbnail-sized editor.
|
||||
#[derive(IntoElement, RegisterComponent, Documented)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct ThemePreviewTile {
|
||||
theme: Arc<Theme>,
|
||||
selected: bool,
|
||||
|
@ -36,10 +34,10 @@ impl RenderOnce for ThemePreviewTile {
|
|||
|
||||
let root_radius = px(8.0);
|
||||
let root_border = px(2.0);
|
||||
let root_padding = px(2.0);
|
||||
let root_padding = px(0.0);
|
||||
let child_border = px(1.0);
|
||||
let inner_radius =
|
||||
inner_corner_radius(root_radius, root_border, root_padding, child_border);
|
||||
CornerSolver::child_radius(root_radius, root_border, root_padding, child_border);
|
||||
|
||||
let item_skeleton = |w: Length, h: Pixels, bg: Hsla| div().w(w).h(h).rounded_full().bg(bg);
|
||||
|
||||
|
@ -200,81 +198,3 @@ impl RenderOnce for ThemePreviewTile {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ThemePreviewTile {
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(Self::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
|
||||
let one_dark = theme_registry.get("One Dark");
|
||||
let one_light = theme_registry.get("One Light");
|
||||
let gruvbox_dark = theme_registry.get("Gruvbox Dark");
|
||||
let gruvbox_light = theme_registry.get("Gruvbox Light");
|
||||
|
||||
let themes_to_preview = vec![
|
||||
one_dark.clone().ok(),
|
||||
one_light.clone().ok(),
|
||||
gruvbox_dark.clone().ok(),
|
||||
gruvbox_light.clone().ok(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.children({
|
||||
if let Some(one_dark) = one_dark.ok() {
|
||||
vec![example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
div()
|
||||
.w(px(240.))
|
||||
.h(px(180.))
|
||||
.child(ThemePreviewTile::new(one_dark.clone(), false, 0.42))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
div()
|
||||
.w(px(240.))
|
||||
.h(px(180.))
|
||||
.child(ThemePreviewTile::new(one_dark, true, 0.42))
|
||||
.into_any_element(),
|
||||
),
|
||||
])]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
.child(
|
||||
example_group(vec![single_example(
|
||||
"Default Themes",
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.children(
|
||||
themes_to_preview
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, theme)| {
|
||||
div().w(px(200.)).h(px(140.)).child(ThemePreviewTile::new(
|
||||
theme.clone(),
|
||||
false,
|
||||
0.42,
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into_any_element(),
|
||||
)])
|
||||
.grow(),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ mod disclosure;
|
|||
mod divider;
|
||||
mod dropdown_menu;
|
||||
mod facepile;
|
||||
mod focus_outline;
|
||||
mod group;
|
||||
mod icon;
|
||||
mod image;
|
||||
|
@ -15,6 +16,7 @@ mod indent_guides;
|
|||
mod indicator;
|
||||
mod keybinding;
|
||||
mod keybinding_hint;
|
||||
mod keyboard_navigation;
|
||||
mod label;
|
||||
mod list;
|
||||
mod modal;
|
||||
|
@ -49,6 +51,7 @@ pub use disclosure::*;
|
|||
pub use divider::*;
|
||||
pub use dropdown_menu::*;
|
||||
pub use facepile::*;
|
||||
pub use focus_outline::*;
|
||||
pub use group::*;
|
||||
pub use icon::*;
|
||||
pub use image::*;
|
||||
|
@ -56,6 +59,7 @@ pub use indent_guides::*;
|
|||
pub use indicator::*;
|
||||
pub use keybinding::*;
|
||||
pub use keybinding_hint::*;
|
||||
pub use keyboard_navigation::*;
|
||||
pub use label::*;
|
||||
pub use list::*;
|
||||
pub use modal::*;
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use gpui::{
|
||||
AnyElement, App, BoxShadow, IntoElement, ParentElement, RenderOnce, Styled, Window, div, hsla,
|
||||
point, px,
|
||||
};
|
||||
use gpui::{AnyElement, App, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
|
||||
use theme::ActiveTheme;
|
||||
|
||||
use crate::{ElevationIndex, h_flex};
|
||||
|
@ -41,11 +38,6 @@ impl RenderOnce for SplitButton {
|
|||
)
|
||||
.child(self.right)
|
||||
.bg(ElevationIndex::Surface.on_elevation_bg(cx))
|
||||
.shadow(vec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.16),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(0.),
|
||||
spread_radius: px(0.),
|
||||
}])
|
||||
.shadow_hairline()
|
||||
}
|
||||
}
|
||||
|
|
65
crates/ui/src/components/focus_outline.rs
Normal file
65
crates/ui/src/components/focus_outline.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use gpui::{
|
||||
AnyElement, IntoElement, ParentElement, Pixels, RenderOnce, Styled, px, transparent_black,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
use crate::{h_flex, utils::CornerSolver};
|
||||
|
||||
/// An outline is a stylistic focus indicator that draws a ring around
|
||||
/// an element with some space between the element and ring.
|
||||
#[derive(IntoElement)]
|
||||
pub struct FocusOutline {
|
||||
corner_radius: Pixels,
|
||||
border_width: Pixels,
|
||||
padding: Pixels,
|
||||
focused: bool,
|
||||
active: bool,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl FocusOutline {
|
||||
pub fn new(child_corner_radius: Pixels, focused: bool, offset: Pixels) -> Self {
|
||||
let ring_width = px(1.);
|
||||
let corner_radius =
|
||||
CornerSolver::parent_radius(child_corner_radius, ring_width, offset, px(0.));
|
||||
Self {
|
||||
corner_radius,
|
||||
border_width: ring_width,
|
||||
padding: offset,
|
||||
focused,
|
||||
active: false,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(mut self, active: bool) -> Self {
|
||||
self.active = active;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for FocusOutline {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
let border_color = if self.focused && self.active {
|
||||
cx.theme().colors().border_focused.opacity(0.48)
|
||||
} else if self.focused {
|
||||
cx.theme().colors().border_variant
|
||||
} else {
|
||||
transparent_black()
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.border(self.border_width)
|
||||
.border_color(border_color)
|
||||
.rounded(self.corner_radius)
|
||||
.p(self.padding)
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for FocusOutline {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
|
@ -12,11 +12,13 @@ use crate::prelude::*;
|
|||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum VectorName {
|
||||
AiGrid,
|
||||
AtomLogo,
|
||||
DebuggerGrid,
|
||||
Grid,
|
||||
SublimeLogo,
|
||||
ZedLogo,
|
||||
ZedXCopilot,
|
||||
Grid,
|
||||
AiGrid,
|
||||
DebuggerGrid,
|
||||
}
|
||||
|
||||
impl VectorName {
|
||||
|
|
59
crates/ui/src/components/keyboard_navigation.rs
Normal file
59
crates/ui/src/components/keyboard_navigation.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use gpui::{Focusable, actions};
|
||||
|
||||
actions!(
|
||||
keyboard_navigation,
|
||||
[NextRow, PreviousRow, NextColumn, PreviousColumn]
|
||||
);
|
||||
|
||||
/// Implement this trait to enable grid-like keyboard navigation for a layout.
|
||||
///
|
||||
/// This trait allows you to navigate through a layout of rows with mixed column
|
||||
/// lengths. In example, a layout might have rows with 5, 1 and 3 columns.
|
||||
///
|
||||
/// Moving up or down between rows will focus the first element in the next or previous row.
|
||||
/// Moving left or right between columns will focus the next or previous element in the same row.
|
||||
///
|
||||
/// Wrapping can be enabled via `vertical_wrapping` and `horizontal_wrapping` respectively.
|
||||
pub trait KeyboardNavigation: Focusable {
|
||||
fn has_focus(&self) -> bool;
|
||||
/// The focused row. Always has a value to allow for "focused inactive" states.
|
||||
fn focused_row(&self) -> usize;
|
||||
/// The focused column. Always has a value to allow for "focused inactive" states.
|
||||
fn focused_column(&self) -> usize;
|
||||
/// Focus the first focusable element in the layout.
|
||||
fn focus_first(&self);
|
||||
/// Focus the next row, wrapping back to the first row if necessary.
|
||||
///
|
||||
/// Is a no-op if wrapping is not enabled.
|
||||
fn focus_next_row(&self);
|
||||
/// Focus the previous row, wrapping back to the last row if necessary.
|
||||
///
|
||||
/// Is a no-op if wrapping is not enabled.
|
||||
fn focus_previous_row(&self);
|
||||
/// Focus the next column, wrapping back to the first column if necessary.
|
||||
///
|
||||
/// Is a no-op if wrapping is not enabled.
|
||||
fn focus_next_column(&self);
|
||||
/// Focus the previous column, wrapping back to the last column if necessary.
|
||||
///
|
||||
/// Is a no-op if wrapping is not enabled.
|
||||
fn focus_previous_column(&self);
|
||||
/// Focus the row at the given index.
|
||||
fn focus_row_index(&self, index: usize);
|
||||
/// Focus the column at the given index.
|
||||
fn focus_column_index(&self, ix: usize);
|
||||
/// When reaching the last row, should moving down wrap
|
||||
/// back to the first row, and vice versa?
|
||||
fn vertical_wrap(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// When reaching the last column, should moving right wrap
|
||||
/// back to the first column, and vice versa?
|
||||
fn horizontal_wrap(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NavigationRow {}
|
||||
|
||||
pub struct NavigationColumn {}
|
|
@ -10,7 +10,7 @@ mod search_input;
|
|||
mod with_rem_size;
|
||||
|
||||
pub use color_contrast::*;
|
||||
pub use corner_solver::{CornerSolver, inner_corner_radius};
|
||||
pub use corner_solver::{CornerSolver, NestedCornerSolver};
|
||||
pub use format_distance::*;
|
||||
pub use search_input::*;
|
||||
pub use with_rem_size::*;
|
||||
|
|
|
@ -1,61 +1,196 @@
|
|||
use gpui::Pixels;
|
||||
|
||||
/// Calculates the child’s content-corner radius for a single nested level.
|
||||
/// Calculates corner radii for nested elements in both directions.
|
||||
///
|
||||
/// child_content_radius = max(0, parent_radius - parent_border - parent_padding + self_border)
|
||||
/// ## Forward calculation (parent → child)
|
||||
/// Given a parent's corner radius, calculates the child's corner radius:
|
||||
/// ```
|
||||
/// child_radius = max(0, parent_radius - parent_border - parent_padding + child_border)
|
||||
/// ```
|
||||
///
|
||||
/// - parent_radius: outer corner radius of the parent element
|
||||
/// - parent_border: border width of the parent element
|
||||
/// - parent_padding: padding of the parent element
|
||||
/// - self_border: border width of this child element (for content inset)
|
||||
pub fn inner_corner_radius(
|
||||
parent_radius: Pixels,
|
||||
parent_border: Pixels,
|
||||
parent_padding: Pixels,
|
||||
self_border: Pixels,
|
||||
) -> Pixels {
|
||||
(parent_radius - parent_border - parent_padding + self_border).max(Pixels::ZERO)
|
||||
}
|
||||
|
||||
/// Solver for arbitrarily deep nested corner radii.
|
||||
///
|
||||
/// Each nested level’s outer border-box radius is:
|
||||
/// R₀ = max(0, root_radius - root_border - root_padding)
|
||||
/// Rᵢ = max(0, Rᵢ₋₁ - childᵢ₋₁_border - childᵢ₋₁_padding) for i > 0
|
||||
pub struct CornerSolver {
|
||||
root_radius: Pixels,
|
||||
root_border: Pixels,
|
||||
root_padding: Pixels,
|
||||
children: Vec<(Pixels, Pixels)>, // (border, padding)
|
||||
}
|
||||
/// ## Inverse calculation (child → parent)
|
||||
/// Given a child's desired corner radius, calculates the required parent radius:
|
||||
/// ```
|
||||
/// parent_radius = child_radius + parent_border + parent_padding - child_border
|
||||
/// ```
|
||||
pub struct CornerSolver;
|
||||
|
||||
impl CornerSolver {
|
||||
pub fn new(root_radius: Pixels, root_border: Pixels, root_padding: Pixels) -> Self {
|
||||
Self {
|
||||
root_radius,
|
||||
root_border,
|
||||
root_padding,
|
||||
children: Vec::new(),
|
||||
}
|
||||
/// Calculates the child's corner radius given the parent's properties.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `parent_radius`: Outer corner radius of the parent element
|
||||
/// - `parent_border`: Border width of the parent element
|
||||
/// - `parent_padding`: Padding of the parent element
|
||||
/// - `child_border`: Border width of the child element
|
||||
pub fn child_radius(
|
||||
parent_radius: Pixels,
|
||||
parent_border: Pixels,
|
||||
parent_padding: Pixels,
|
||||
child_border: Pixels,
|
||||
) -> Pixels {
|
||||
(parent_radius - parent_border - parent_padding + child_border).max(Pixels::ZERO)
|
||||
}
|
||||
|
||||
pub fn add_child(mut self, border: Pixels, padding: Pixels) -> Self {
|
||||
self.children.push((border, padding));
|
||||
/// Calculates the required parent radius to achieve a desired child radius.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `child_radius`: Desired corner radius for the child element
|
||||
/// - `parent_border`: Border width of the parent element
|
||||
/// - `parent_padding`: Padding of the parent element
|
||||
/// - `child_border`: Border width of the child element
|
||||
pub fn parent_radius(
|
||||
child_radius: Pixels,
|
||||
parent_border: Pixels,
|
||||
parent_padding: Pixels,
|
||||
child_border: Pixels,
|
||||
) -> Pixels {
|
||||
child_radius + parent_border + parent_padding - child_border
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for calculating corner radii across multiple nested levels.
|
||||
pub struct NestedCornerSolver {
|
||||
levels: Vec<Level>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Level {
|
||||
border: Pixels,
|
||||
padding: Pixels,
|
||||
}
|
||||
|
||||
impl NestedCornerSolver {
|
||||
/// Creates a new nested corner solver.
|
||||
pub fn new() -> Self {
|
||||
Self { levels: Vec::new() }
|
||||
}
|
||||
|
||||
/// Adds a level to the nesting hierarchy.
|
||||
///
|
||||
/// Levels should be added from outermost to innermost.
|
||||
pub fn add_level(mut self, border: Pixels, padding: Pixels) -> Self {
|
||||
self.levels.push(Level { border, padding });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn corner_radius(&self, level: usize) -> Pixels {
|
||||
if level == 0 {
|
||||
return (self.root_radius - self.root_border - self.root_padding).max(Pixels::ZERO);
|
||||
/// Calculates the corner radius at a specific nesting level given the root radius.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `root_radius`: The outermost corner radius
|
||||
/// - `level`: The nesting level (0 = first child of root, 1 = grandchild, etc.)
|
||||
pub fn radius_at_level(&self, root_radius: Pixels, level: usize) -> Pixels {
|
||||
let mut radius = root_radius;
|
||||
|
||||
for i in 0..=level.min(self.levels.len().saturating_sub(1)) {
|
||||
let current_level = &self.levels[i];
|
||||
let next_border = if i < self.levels.len() - 1 {
|
||||
self.levels[i + 1].border
|
||||
} else {
|
||||
Pixels::ZERO
|
||||
};
|
||||
|
||||
radius = CornerSolver::child_radius(
|
||||
radius,
|
||||
current_level.border,
|
||||
current_level.padding,
|
||||
next_border,
|
||||
);
|
||||
}
|
||||
if level >= self.children.len() {
|
||||
return Pixels::ZERO;
|
||||
|
||||
radius
|
||||
}
|
||||
|
||||
/// Calculates the required root radius to achieve a desired radius at a specific level.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `target_radius`: The desired corner radius at the target level
|
||||
/// - `target_level`: The nesting level where the target radius should be achieved
|
||||
pub fn root_radius_for_level(&self, target_radius: Pixels, target_level: usize) -> Pixels {
|
||||
if target_level >= self.levels.len() {
|
||||
return target_radius;
|
||||
}
|
||||
let mut r = (self.root_radius - self.root_border - self.root_padding).max(Pixels::ZERO);
|
||||
for i in 0..level {
|
||||
let (b, p) = self.children[i];
|
||||
r = (r - b - p).max(Pixels::ZERO);
|
||||
|
||||
let mut radius = target_radius;
|
||||
|
||||
// Work backwards from the target level to the root
|
||||
for i in (0..=target_level).rev() {
|
||||
let current_level = &self.levels[i];
|
||||
let child_border = if i < self.levels.len() - 1 {
|
||||
self.levels[i + 1].border
|
||||
} else {
|
||||
Pixels::ZERO
|
||||
};
|
||||
|
||||
radius = CornerSolver::parent_radius(
|
||||
radius,
|
||||
current_level.border,
|
||||
current_level.padding,
|
||||
child_border,
|
||||
);
|
||||
}
|
||||
r
|
||||
|
||||
radius
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_forward_calculation() {
|
||||
let parent_radius = Pixels(20.0);
|
||||
let parent_border = Pixels(2.0);
|
||||
let parent_padding = Pixels(8.0);
|
||||
let child_border = Pixels(1.0);
|
||||
|
||||
let child_radius =
|
||||
CornerSolver::child_radius(parent_radius, parent_border, parent_padding, child_border);
|
||||
|
||||
assert_eq!(child_radius, Pixels(11.0)); // 20 - 2 - 8 + 1 = 11
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse_calculation() {
|
||||
let child_radius = Pixels(11.0);
|
||||
let parent_border = Pixels(2.0);
|
||||
let parent_padding = Pixels(8.0);
|
||||
let child_border = Pixels(1.0);
|
||||
|
||||
let parent_radius =
|
||||
CornerSolver::parent_radius(child_radius, parent_border, parent_padding, child_border);
|
||||
|
||||
assert_eq!(parent_radius, Pixels(20.0)); // 11 + 2 + 8 - 1 = 20
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_forward() {
|
||||
let solver = NestedCornerSolver::new()
|
||||
.add_level(Pixels(2.0), Pixels(8.0)) // Root level
|
||||
.add_level(Pixels(1.0), Pixels(4.0)) // First child
|
||||
.add_level(Pixels(1.0), Pixels(2.0)); // Second child
|
||||
|
||||
let root_radius = Pixels(20.0);
|
||||
|
||||
assert_eq!(solver.radius_at_level(root_radius, 0), Pixels(11.0)); // 20 - 2 - 8 + 1
|
||||
assert_eq!(solver.radius_at_level(root_radius, 1), Pixels(7.0)); // 11 - 1 - 4 + 1
|
||||
assert_eq!(solver.radius_at_level(root_radius, 2), Pixels(4.0)); // 7 - 1 - 2 + 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_inverse() {
|
||||
let solver = NestedCornerSolver::new()
|
||||
.add_level(Pixels(2.0), Pixels(8.0)) // Root level
|
||||
.add_level(Pixels(1.0), Pixels(4.0)) // First child
|
||||
.add_level(Pixels(1.0), Pixels(2.0)); // Second child
|
||||
|
||||
let target_radius = Pixels(4.0);
|
||||
let root_radius = solver.root_radius_for_level(target_radius, 2);
|
||||
|
||||
assert_eq!(root_radius, Pixels(20.0));
|
||||
|
||||
// Verify by calculating forward
|
||||
assert_eq!(solver.radius_at_level(root_radius, 2), target_radius);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ pub use multibuffer_hint::*;
|
|||
mod base_keymap_picker;
|
||||
mod base_keymap_setting;
|
||||
mod multibuffer_hint;
|
||||
mod welcome_ui;
|
||||
|
||||
actions!(
|
||||
welcome,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
mod theme_preview;
|
|
@ -21,8 +21,8 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
activity_indicator.workspace = true
|
||||
agent.workspace = true
|
||||
agent_ui.workspace = true
|
||||
agent_settings.workspace = true
|
||||
agent_ui.workspace = true
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
assets.workspace = true
|
||||
|
@ -42,6 +42,7 @@ client.workspace = true
|
|||
collab_ui.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
component.workspace = true
|
||||
copilot.workspace = true
|
||||
dap_adapters.workspace = true
|
||||
|
@ -70,7 +71,6 @@ gpui = { workspace = true, features = [
|
|||
"windows-manifest",
|
||||
] }
|
||||
gpui_tokio.workspace = true
|
||||
|
||||
http_client.workspace = true
|
||||
image_viewer.workspace = true
|
||||
indoc.workspace = true
|
||||
|
@ -90,13 +90,13 @@ libc.workspace = true
|
|||
log.workspace = true
|
||||
markdown.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
svg_preview.workspace = true
|
||||
menu.workspace = true
|
||||
migrator.workspace = true
|
||||
mimalloc = { version = "0.1", optional = true }
|
||||
nix = { workspace = true, features = ["pthread", "signal"] }
|
||||
node_runtime.workspace = true
|
||||
notifications.workspace = true
|
||||
onboarding_ui.workspace = true
|
||||
outline.workspace = true
|
||||
outline_panel.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
@ -125,6 +125,7 @@ smol.workspace = true
|
|||
snippet_provider.workspace = true
|
||||
snippets_ui.workspace = true
|
||||
supermaven.workspace = true
|
||||
svg_preview.workspace = true
|
||||
sysinfo.workspace = true
|
||||
tab_switcher.workspace = true
|
||||
task.workspace = true
|
||||
|
|
|
@ -583,6 +583,7 @@ pub fn main() {
|
|||
collab_ui::init(&app_state, cx);
|
||||
git_ui::init(cx);
|
||||
jj_ui::init(cx);
|
||||
onboarding_ui::init(cx);
|
||||
feedback::init(cx);
|
||||
markdown_preview::init(cx);
|
||||
svg_preview::init(cx);
|
||||
|
|
|
@ -16,6 +16,7 @@ use assets::Assets;
|
|||
use breadcrumbs::Breadcrumbs;
|
||||
use client::zed_urls;
|
||||
use collections::VecDeque;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use debugger_ui::debugger_panel::DebugPanel;
|
||||
use editor::ProposedChangesEditorToolbar;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
|
@ -52,6 +53,7 @@ use settings::{
|
|||
Settings, SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content,
|
||||
initial_project_settings_content, initial_tasks_content, update_settings_file,
|
||||
};
|
||||
use std::any::TypeId;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{self, AtomicBool};
|
||||
use std::{borrow::Cow, path::Path, sync::Arc};
|
||||
|
@ -72,7 +74,8 @@ use workspace::{
|
|||
use workspace::{CloseIntent, CloseWindow, RestoreBanner, with_active_or_new_workspace};
|
||||
use workspace::{Pane, notifications::DetachAndPromptErr};
|
||||
use zed_actions::{
|
||||
OpenAccountSettings, OpenBrowser, OpenDocs, OpenServerSettings, OpenSettings, OpenZedUrl, Quit,
|
||||
DisableAiAssistance, EnableAiAssistance, OpenAccountSettings, OpenBrowser, OpenDocs,
|
||||
OpenServerSettings, OpenSettings, OpenZedUrl, Quit,
|
||||
};
|
||||
|
||||
actions!(
|
||||
|
@ -215,6 +218,35 @@ pub fn init(cx: &mut App) {
|
|||
);
|
||||
});
|
||||
});
|
||||
cx.on_action(|_: &EnableAiAssistance, cx| {
|
||||
with_active_or_new_workspace(cx, |workspace, _, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
language::language_settings::AllLanguageSettings::set_ai_assistance(true, fs, cx);
|
||||
});
|
||||
});
|
||||
cx.on_action(|_: &DisableAiAssistance, cx| {
|
||||
with_active_or_new_workspace(cx, |workspace, _, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
language::language_settings::AllLanguageSettings::set_ai_assistance(false, fs, cx);
|
||||
});
|
||||
});
|
||||
|
||||
// Filter AI assistance actions based on current state
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let ai_enabled =
|
||||
language::language_settings::all_language_settings(None, cx).is_ai_assistance_enabled();
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
if ai_enabled {
|
||||
filter.hide_action_types(&[TypeId::of::<EnableAiAssistance>()]);
|
||||
filter.show_action_types([TypeId::of::<DisableAiAssistance>()].iter());
|
||||
} else {
|
||||
filter.show_action_types([TypeId::of::<EnableAiAssistance>()].iter());
|
||||
filter.hide_action_types(&[TypeId::of::<DisableAiAssistance>()]);
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn bind_on_window_closed(cx: &mut App) -> Option<gpui::Subscription> {
|
||||
|
|
|
@ -50,6 +50,10 @@ actions!(
|
|||
OpenLicenses,
|
||||
/// Opens the telemetry log.
|
||||
OpenTelemetryLog,
|
||||
/// Enables AI assistance features.
|
||||
EnableAiAssistance,
|
||||
/// Disables AI assistance features.
|
||||
DisableAiAssistance,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue