onboarding ui: Add theme preview tiles and button functionality to basic page (#35413)

This PR polishes and adds functionality to the onboarding UI with a
focus on the basic page. It added theme preview tiles, got the Vim,
telemetry, crash reporting, and sign-in button working.

The theme preview component was moved to the UI crate and it now can
have a click handler on it.

Finally, this commit also changed `client::User.github_login` and
`client::UserStore.by_github_login` to use `SharedStrings` instead of
`Strings`. This change was made because user.github_login was cloned in
several areas including the UI, and was cast to a shared string in some
cases too.

Release Notes:

- N/A

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid 2025-07-31 14:40:41 -04:00 committed by GitHub
parent b59f992928
commit c6947ee4f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 295 additions and 83 deletions

3
Cargo.lock generated
View file

@ -10923,6 +10923,7 @@ name = "onboarding"
version = "0.1.0"
dependencies = [
"anyhow",
"client",
"command_palette_hooks",
"db",
"editor",
@ -10937,6 +10938,7 @@ dependencies = [
"theme",
"ui",
"util",
"vim_mode_setting",
"workspace",
"workspace-hack",
"zed_actions",
@ -18594,7 +18596,6 @@ dependencies = [
"serde",
"settings",
"telemetry",
"theme",
"ui",
"util",
"vim_mode_setting",

View file

@ -126,7 +126,7 @@ impl ChannelMembership {
proto::channel_member::Kind::Member => 0,
proto::channel_member::Kind::Invitee => 1,
},
username_order: self.user.github_login.as_str(),
username_order: &self.user.github_login,
}
}
}

View file

@ -55,7 +55,7 @@ pub struct ParticipantIndex(pub u32);
#[derive(Default, Debug)]
pub struct User {
pub id: UserId,
pub github_login: String,
pub github_login: SharedString,
pub avatar_uri: SharedUri,
pub name: Option<String>,
}
@ -107,7 +107,7 @@ pub enum ContactRequestStatus {
pub struct UserStore {
users: HashMap<u64, Arc<User>>,
by_github_login: HashMap<String, u64>,
by_github_login: HashMap<SharedString, u64>,
participant_indices: HashMap<u64, ParticipantIndex>,
update_contacts_tx: mpsc::UnboundedSender<UpdateContacts>,
current_plan: Option<proto::Plan>,
@ -904,7 +904,7 @@ impl UserStore {
let mut missing_user_ids = Vec::new();
for id in user_ids {
if let Some(github_login) = self.get_cached_user(id).map(|u| u.github_login.clone()) {
ret.insert(id, github_login.into());
ret.insert(id, github_login);
} else {
missing_user_ids.push(id)
}
@ -925,7 +925,7 @@ impl User {
fn new(message: proto::User) -> Arc<Self> {
Arc::new(User {
id: message.id,
github_login: message.github_login,
github_login: message.github_login.into(),
avatar_uri: message.avatar_url.into(),
name: message.name,
})

View file

@ -38,12 +38,12 @@ fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomPartic
let mut remote = room
.remote_participants()
.values()
.map(|participant| participant.user.github_login.clone())
.map(|participant| participant.user.github_login.clone().to_string())
.collect::<Vec<_>>();
let mut pending = room
.pending_participants()
.iter()
.map(|user| user.github_login.clone())
.map(|user| user.github_login.clone().to_string())
.collect::<Vec<_>>();
remote.sort();
pending.sort();

View file

@ -1881,7 +1881,7 @@ async fn test_active_call_events(
vec![room::Event::RemoteProjectShared {
owner: Arc::new(User {
id: client_a.user_id().unwrap(),
github_login: "user_a".to_string(),
github_login: "user_a".into(),
avatar_uri: "avatar_a".into(),
name: None,
}),
@ -1900,7 +1900,7 @@ async fn test_active_call_events(
vec![room::Event::RemoteProjectShared {
owner: Arc::new(User {
id: client_b.user_id().unwrap(),
github_login: "user_b".to_string(),
github_login: "user_b".into(),
avatar_uri: "avatar_b".into(),
name: None,
}),
@ -6079,7 +6079,7 @@ async fn test_contacts(
.iter()
.map(|contact| {
(
contact.user.github_login.clone(),
contact.user.github_login.clone().to_string(),
if contact.online { "online" } else { "offline" },
if contact.busy { "busy" } else { "free" },
)

View file

@ -696,17 +696,17 @@ impl TestClient {
current: store
.contacts()
.iter()
.map(|contact| contact.user.github_login.clone())
.map(|contact| contact.user.github_login.clone().to_string())
.collect(),
outgoing_requests: store
.outgoing_contact_requests()
.iter()
.map(|user| user.github_login.clone())
.map(|user| user.github_login.clone().to_string())
.collect(),
incoming_requests: store
.incoming_contact_requests()
.iter()
.map(|user| user.github_login.clone())
.map(|user| user.github_login.clone().to_string())
.collect(),
})
}

View file

@ -940,7 +940,7 @@ impl CollabPanel {
room.read(cx).local_participant().role == proto::ChannelRole::Admin
});
ListItem::new(SharedString::from(user.github_login.clone()))
ListItem::new(user.github_login.clone())
.start_slot(Avatar::new(user.avatar_uri.clone()))
.child(Label::new(user.github_login.clone()))
.toggle_state(is_selected)
@ -2583,7 +2583,7 @@ impl CollabPanel {
) -> impl IntoElement {
let online = contact.online;
let busy = contact.busy || calling;
let github_login = SharedString::from(contact.user.github_login.clone());
let github_login = contact.user.github_login.clone();
let item = ListItem::new(github_login.clone())
.indent_level(1)
.indent_step_size(px(20.))
@ -2662,7 +2662,7 @@ impl CollabPanel {
is_selected: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
let github_login = SharedString::from(user.github_login.clone());
let github_login = user.github_login.clone();
let user_id = user.id;
let is_response_pending = self.user_store.read(cx).is_contact_request_pending(user);
let color = if is_response_pending {

View file

@ -2416,7 +2416,7 @@ impl GitPanel {
.committer_name
.clone()
.or_else(|| participant.user.name.clone())
.unwrap_or_else(|| participant.user.github_login.clone());
.unwrap_or_else(|| participant.user.github_login.clone().to_string());
new_co_authors.push((name.clone(), email.clone()))
}
}
@ -2436,7 +2436,7 @@ impl GitPanel {
.name
.clone()
.or_else(|| user.name.clone())
.unwrap_or_else(|| user.github_login.clone());
.unwrap_or_else(|| user.github_login.clone().to_string());
Some((name, email))
}

View file

@ -16,6 +16,7 @@ default = []
[dependencies]
anyhow.workspace = true
client.workspace = true
command_palette_hooks.workspace = true
db.workspace = true
editor.workspace = true
@ -30,6 +31,7 @@ settings.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
vim_mode_setting.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true

View file

@ -1,16 +1,28 @@
use fs::Fs;
use gpui::{App, IntoElement, Window};
use settings::{Settings, update_settings_file};
use theme::{ThemeMode, ThemeSettings};
use ui::{SwitchField, ToggleButtonGroup, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*};
use std::sync::Arc;
fn read_theme_selection(cx: &App) -> ThemeMode {
use client::TelemetrySettings;
use fs::Fs;
use gpui::{App, IntoElement};
use settings::{BaseKeymap, Settings, update_settings_file};
use theme::{Appearance, SystemAppearance, ThemeMode, ThemeSettings};
use ui::{
SwitchField, ThemePreviewTile, ToggleButtonGroup, ToggleButtonSimple, ToggleButtonWithIcon,
prelude::*,
};
use vim_mode_setting::VimModeSetting;
use crate::Onboarding;
fn read_theme_selection(cx: &App) -> (ThemeMode, SharedString) {
let settings = ThemeSettings::get_global(cx);
settings
.theme_selection
.as_ref()
.and_then(|selection| selection.mode())
.unwrap_or_default()
(
settings
.theme_selection
.as_ref()
.and_then(|selection| selection.mode())
.unwrap_or_default(),
settings.active_theme.name.clone(),
)
}
fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
@ -21,9 +33,15 @@ fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
});
}
fn render_theme_section(cx: &mut App) -> impl IntoElement {
let theme_mode = read_theme_selection(cx);
fn write_keymap_base(keymap_base: BaseKeymap, cx: &App) {
let fs = <dyn Fs>::global(cx);
update_settings_file::<BaseKeymap>(fs, cx, move |setting, _| {
*setting = Some(keymap_base);
});
}
fn render_theme_section(theme_mode: ThemeMode) -> impl IntoElement {
h_flex().justify_between().child(Label::new("Theme")).child(
ToggleButtonGroup::single_row(
"theme-selector-onboarding",
@ -49,55 +67,160 @@ fn render_theme_section(cx: &mut App) -> impl IntoElement {
)
}
fn render_telemetry_section() -> impl IntoElement {
fn render_telemetry_section(fs: Arc<dyn Fs>, cx: &App) -> impl IntoElement {
v_flex()
.gap_3()
.gap_4()
.child(Label::new("Telemetry").size(LabelSize::Large))
.child(SwitchField::new(
"vim_mode",
"onboarding-telemetry-metrics",
"Help Improve Zed",
"Sending anonymous usage data helps us build the right features and create the best experience.",
ui::ToggleState::Selected,
|_, _, _| {},
if TelemetrySettings::get_global(cx).metrics {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
{
let fs = fs.clone();
move |selection, _, cx| {
let enabled = match selection {
ToggleState::Selected => true,
ToggleState::Unselected => false,
ToggleState::Indeterminate => { return; },
};
update_settings_file::<TelemetrySettings>(
fs.clone(),
cx,
move |setting, _| setting.metrics = Some(enabled),
);
}},
))
.child(SwitchField::new(
"vim_mode",
"onboarding-telemetry-crash-reports",
"Help Fix Zed",
"Send crash reports so we can fix critical issues fast.",
ui::ToggleState::Selected,
|_, _, _| {},
if TelemetrySettings::get_global(cx).diagnostics {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
{
let fs = fs.clone();
move |selection, _, cx| {
let enabled = match selection {
ToggleState::Selected => true,
ToggleState::Unselected => false,
ToggleState::Indeterminate => { return; },
};
update_settings_file::<TelemetrySettings>(
fs.clone(),
cx,
move |setting, _| setting.diagnostics = Some(enabled),
);
}
}
))
}
pub(crate) fn render_basics_page(_: &mut Window, cx: &mut App) -> impl IntoElement {
pub(crate) fn render_basics_page(onboarding: &Onboarding, cx: &mut App) -> impl IntoElement {
let (theme_mode, active_theme_name) = read_theme_selection(cx);
let themes = match theme_mode {
ThemeMode::Dark => &onboarding.dark_themes,
ThemeMode::Light => &onboarding.light_themes,
ThemeMode::System => match SystemAppearance::global(cx).0 {
Appearance::Light => &onboarding.light_themes,
Appearance::Dark => &onboarding.dark_themes,
},
};
let base_keymap = match BaseKeymap::get_global(cx) {
BaseKeymap::VSCode => Some(0),
BaseKeymap::JetBrains => Some(1),
BaseKeymap::SublimeText => Some(2),
BaseKeymap::Atom => Some(3),
BaseKeymap::Emacs => Some(4),
BaseKeymap::Cursor => Some(5),
BaseKeymap::TextMate | BaseKeymap::None => None,
};
v_flex()
.gap_6()
.child(render_theme_section(cx))
.child(render_theme_section(theme_mode))
.child(h_flex().children(
themes.iter().map(|theme| {
ThemePreviewTile::new(theme.clone(), active_theme_name == theme.name, 0.48)
.on_click({
let theme_name = theme.name.clone();
let fs = onboarding.fs.clone();
move |_, _, cx| {
let theme_name = theme_name.clone();
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
settings.set_theme(theme_name.to_string(), SystemAppearance::global(cx).0);
});
}
})
})
))
.child(
v_flex().gap_2().child(Label::new("Base Keymap")).child(
ToggleButtonGroup::two_rows(
"multiple_row_test",
[
ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, _| {}),
ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, _| {}),
ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, _| {}),
ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, cx| {
write_keymap_base(BaseKeymap::VSCode, cx);
}),
ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, cx| {
write_keymap_base(BaseKeymap::JetBrains, cx);
}),
ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, cx| {
write_keymap_base(BaseKeymap::SublimeText, cx);
}),
],
[
ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, _| {}),
ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, _| {}),
ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, _| {}),
ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, cx| {
write_keymap_base(BaseKeymap::Atom, cx);
}),
ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, cx| {
write_keymap_base(BaseKeymap::Emacs, cx);
}),
ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, cx| {
write_keymap_base(BaseKeymap::Cursor, cx);
}),
],
)
.when_some(base_keymap, |this, base_keymap| this.selected_index(base_keymap))
.button_width(rems_from_px(230.))
.style(ui::ToggleButtonGroupStyle::Outlined)
),
)
.child(v_flex().justify_center().child(div().h_0().child("hack").invisible()).child(SwitchField::new(
"vim_mode",
"onboarding-vim-mode",
"Vim Mode",
"Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.",
ui::ToggleState::Selected,
|_, _, _| {},
if VimModeSetting::get_global(cx).0 {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
{
let fs = onboarding.fs.clone();
move |selection, _, cx| {
let enabled = match selection {
ToggleState::Selected => true,
ToggleState::Unselected => false,
ToggleState::Indeterminate => { return; },
};
update_settings_file::<VimModeSetting>(
fs.clone(),
cx,
move |setting, _| *setting = Some(enabled),
);
}
},
)))
.child(render_telemetry_section())
.child(render_telemetry_section(onboarding.fs.clone(), cx))
}

View file

@ -1,4 +1,5 @@
use crate::welcome::{ShowWelcome, WelcomePage};
use client::{Client, UserStore};
use command_palette_hooks::CommandPaletteFilter;
use db::kvp::KEY_VALUE_STORE;
use feature_flags::{FeatureFlag, FeatureFlagViewExt as _};
@ -12,11 +13,13 @@ use schemars::JsonSchema;
use serde::Deserialize;
use settings::{SettingsStore, VsCodeSettingsSource};
use std::sync::Arc;
use ui::{FluentBuilder, KeyBinding, Vector, VectorName, prelude::*, rems_from_px};
use theme::{Theme, ThemeRegistry};
use ui::{Avatar, FluentBuilder, KeyBinding, Vector, VectorName, prelude::*, rems_from_px};
use workspace::{
AppState, Workspace, WorkspaceId,
dock::DockPosition,
item::{Item, ItemEvent},
notifications::NotifyResultExt as _,
open_new, with_active_or_new_workspace,
};
@ -72,7 +75,11 @@ pub fn init(cx: &mut App) {
if let Some(existing) = existing {
workspace.activate_item(&existing, true, true, window, cx);
} else {
let settings_page = Onboarding::new(workspace.weak_handle(), cx);
let settings_page = Onboarding::new(
workspace.weak_handle(),
workspace.user_store().clone(),
cx,
);
workspace.add_item_to_active_pane(
Box::new(settings_page),
None,
@ -188,7 +195,8 @@ pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyh
|workspace, window, cx| {
{
workspace.toggle_dock(DockPosition::Left, window, cx);
let onboarding_page = Onboarding::new(workspace.weak_handle(), cx);
let onboarding_page =
Onboarding::new(workspace.weak_handle(), workspace.user_store().clone(), cx);
workspace.add_item_to_center(Box::new(onboarding_page.clone()), window, cx);
window.focus(&onboarding_page.focus_handle(cx));
@ -211,17 +219,51 @@ enum SelectedPage {
struct Onboarding {
workspace: WeakEntity<Workspace>,
light_themes: [Arc<Theme>; 3],
dark_themes: [Arc<Theme>; 3],
focus_handle: FocusHandle,
selected_page: SelectedPage,
fs: Arc<dyn Fs>,
user_store: Entity<UserStore>,
_settings_subscription: Subscription,
}
impl Onboarding {
fn new(workspace: WeakEntity<Workspace>, cx: &mut App) -> Entity<Self> {
fn new(
workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>,
cx: &mut App,
) -> Entity<Self> {
let theme_registry = ThemeRegistry::global(cx);
let one_dark = theme_registry
.get("One Dark")
.expect("Default themes are always present");
let ayu_dark = theme_registry
.get("Ayu Dark")
.expect("Default themes are always present");
let gruvbox_dark = theme_registry
.get("Gruvbox Dark")
.expect("Default themes are always present");
let one_light = theme_registry
.get("One Light")
.expect("Default themes are always present");
let ayu_light = theme_registry
.get("Ayu Light")
.expect("Default themes are always present");
let gruvbox_light = theme_registry
.get("Gruvbox Light")
.expect("Default themes are always present");
cx.new(|cx| Self {
workspace,
user_store,
focus_handle: cx.focus_handle(),
light_themes: [one_light, ayu_light, gruvbox_light],
dark_themes: [one_dark, ayu_dark, gruvbox_dark],
selected_page: SelectedPage::Basics,
fs: <dyn Fs>::global(cx),
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
})
}
@ -339,16 +381,37 @@ impl Onboarding {
),
)
.child(
Button::new("sign_in", "Sign In")
.style(ButtonStyle::Outlined)
.full_width(),
if let Some(user) = self.user_store.read(cx).current_user() {
h_flex()
.gap_2()
.child(Avatar::new(user.avatar_uri.clone()))
.child(Label::new(user.github_login.clone()))
.into_any_element()
} else {
Button::new("sign_in", "Sign In")
.style(ButtonStyle::Outlined)
.full_width()
.on_click(|_, window, cx| {
let client = Client::global(cx);
window
.spawn(cx, async move |cx| {
client
.authenticate_and_connect(true, &cx)
.await
.into_response()
.notify_async_err(cx);
})
.detach();
})
.into_any_element()
},
)
}
fn render_page(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
match self.selected_page {
SelectedPage::Basics => {
crate::basics_page::render_basics_page(window, cx).into_any_element()
crate::basics_page::render_basics_page(&self, cx).into_any_element()
}
SelectedPage::Editing => {
crate::editing_page::render_editing_page(window, cx).into_any_element()
@ -420,7 +483,11 @@ impl Item for Onboarding {
_: &mut Window,
cx: &mut Context<Self>,
) -> Option<Entity<Self>> {
Some(Onboarding::new(self.workspace.clone(), cx))
Some(Onboarding::new(
self.workspace.clone(),
self.user_store.clone(),
cx,
))
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {

View file

@ -34,6 +34,7 @@ mod stack;
mod sticky_items;
mod tab;
mod tab_bar;
mod theme_preview;
mod toggle;
mod tooltip;
@ -76,6 +77,7 @@ pub use stack::*;
pub use sticky_items::*;
pub use tab::*;
pub use tab_bar::*;
pub use theme_preview::*;
pub use toggle::*;
pub use tooltip::*;

View file

@ -431,15 +431,17 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
{
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let entries = self.rows.into_iter().enumerate().map(|(row_index, row)| {
row.into_iter().enumerate().map(move |(index, button)| {
row.into_iter().enumerate().map(move |(col_index, button)| {
let ButtonConfiguration {
label,
icon,
on_click,
} = button.into_configuration();
ButtonLike::new((self.group_name, row_index * COLS + index))
.when(index == self.selected_index, |this| {
let entry_index = row_index * COLS + col_index;
ButtonLike::new((self.group_name, entry_index))
.when(entry_index == self.selected_index, |this| {
this.toggle_state(true)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
})
@ -451,10 +453,12 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
h_flex()
.min_w(self.button_width)
.gap_1p5()
.px_3()
.py_1()
.justify_center()
.when_some(icon, |this, icon| {
this.child(Icon::new(icon).size(IconSize::XSmall).map(|this| {
if index == self.selected_index {
if entry_index == self.selected_index {
this.color(Color::Accent)
} else {
this.color(Color::Muted)
@ -462,9 +466,11 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
}))
})
.child(
Label::new(label).when(index == self.selected_index, |this| {
this.color(Color::Accent)
}),
Label::new(label)
.size(LabelSize::Small)
.when(entry_index == self.selected_index, |this| {
this.color(Color::Accent)
}),
),
)
.on_click(on_click)

View file

@ -1,10 +1,7 @@
#![allow(unused, dead_code)]
use gpui::{Hsla, Length};
use std::sync::Arc;
use crate::{component_prelude::Documented, prelude::*, utils::inner_corner_radius};
use gpui::{App, ClickEvent, Hsla, IntoElement, Length, RenderOnce, Window};
use std::{rc::Rc, sync::Arc};
use theme::{Theme, ThemeRegistry};
use ui::{
IntoElement, RenderOnce, component_prelude::Documented, prelude::*, utils::inner_corner_radius,
};
/// Shows a preview of a theme as an abstract illustration
/// of a thumbnail-sized editor.
@ -12,6 +9,7 @@ use ui::{
pub struct ThemePreviewTile {
theme: Arc<Theme>,
selected: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
seed: f32,
}
@ -19,8 +17,9 @@ impl ThemePreviewTile {
pub fn new(theme: Arc<Theme>, selected: bool, seed: f32) -> Self {
Self {
theme,
selected,
seed,
selected,
on_click: None,
}
}
@ -28,10 +27,18 @@ impl ThemePreviewTile {
self.selected = selected;
self
}
pub fn on_click(
mut self,
listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Rc::new(listener));
self
}
}
impl RenderOnce for ThemePreviewTile {
fn render(self, _window: &mut ui::Window, _cx: &mut ui::App) -> impl IntoElement {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let color = self.theme.colors();
let root_radius = px(8.0);
@ -181,6 +188,13 @@ impl RenderOnce for ThemePreviewTile {
let content = div().size_full().flex().child(sidebar).child(pane);
div()
// Note: If two theme preview tiles are rendering the same theme they'll share an ID
// this will mean on hover and on click events will be shared between them
.id(SharedString::from(self.theme.id.clone()))
.when_some(self.on_click.clone(), |this, on_click| {
this.on_click(move |event, window, cx| on_click(event, window, cx))
.hover(|style| style.cursor_pointer().border_color(color.element_hover))
})
.size_full()
.rounded(root_radius)
.p(root_padding)
@ -261,7 +275,7 @@ impl Component for ThemePreviewTile {
themes_to_preview
.iter()
.enumerate()
.map(|(i, theme)| {
.map(|(_, theme)| {
div().w(px(200.)).h(px(140.)).child(ThemePreviewTile::new(
theme.clone(),
false,

View file

@ -1,6 +1,6 @@
use gpui::{
AnyElement, AnyView, ClickEvent, CursorStyle, ElementId, Hsla, IntoElement, Styled, Window,
div, hsla, prelude::*,
AnyElement, AnyView, ClickEvent, ElementId, Hsla, IntoElement, Styled, Window, div, hsla,
prelude::*,
};
use std::sync::Arc;
@ -610,7 +610,7 @@ impl RenderOnce for SwitchField {
h_flex()
.id(SharedString::from(format!("{}-container", self.id)))
.when(!self.disabled, |this| {
this.hover(|this| this.cursor(CursorStyle::PointingHand))
this.hover(|this| this.cursor_pointer())
})
.w_full()
.gap_4()

View file

@ -29,7 +29,6 @@ project.workspace = true
serde.workspace = true
settings.workspace = true
telemetry.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
vim_mode_setting.workspace = true

View file

@ -21,7 +21,6 @@ pub use multibuffer_hint::*;
mod base_keymap_picker;
mod multibuffer_hint;
mod welcome_ui;
actions!(
welcome,

View file

@ -1 +0,0 @@
mod theme_preview;