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:
parent
b59f992928
commit
c6947ee4f0
18 changed files with 295 additions and 83 deletions
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
294
crates/ui/src/components/theme_preview.rs
Normal file
294
crates/ui/src/components/theme_preview.rs
Normal file
|
@ -0,0 +1,294 @@
|
|||
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};
|
||||
|
||||
/// Shows a preview of a theme as an abstract illustration
|
||||
/// of a thumbnail-sized editor.
|
||||
#[derive(IntoElement, RegisterComponent, Documented)]
|
||||
pub struct ThemePreviewTile {
|
||||
theme: Arc<Theme>,
|
||||
selected: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
seed: f32,
|
||||
}
|
||||
|
||||
impl ThemePreviewTile {
|
||||
pub fn new(theme: Arc<Theme>, selected: bool, seed: f32) -> Self {
|
||||
Self {
|
||||
theme,
|
||||
seed,
|
||||
selected,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
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 Window, _cx: &mut App) -> impl IntoElement {
|
||||
let color = self.theme.colors();
|
||||
|
||||
let root_radius = px(8.0);
|
||||
let root_border = px(2.0);
|
||||
let root_padding = px(2.0);
|
||||
let child_border = px(1.0);
|
||||
let inner_radius =
|
||||
inner_corner_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);
|
||||
|
||||
let skeleton_height = px(4.);
|
||||
|
||||
let sidebar_seeded_width = |seed: f32, index: usize| {
|
||||
let value = (seed * 1000.0 + index as f32 * 10.0).sin() * 0.5 + 0.5;
|
||||
0.5 + value * 0.45
|
||||
};
|
||||
|
||||
let sidebar_skeleton_items = 8;
|
||||
|
||||
let sidebar_skeleton = (0..sidebar_skeleton_items)
|
||||
.map(|i| {
|
||||
let width = sidebar_seeded_width(self.seed, i);
|
||||
item_skeleton(
|
||||
relative(width).into(),
|
||||
skeleton_height,
|
||||
color.text.alpha(0.45),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let sidebar = div()
|
||||
.h_full()
|
||||
.w(relative(0.25))
|
||||
.border_r(px(1.))
|
||||
.border_color(color.border_transparent)
|
||||
.bg(color.panel_background)
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.gap(px(4.))
|
||||
.children(sidebar_skeleton),
|
||||
);
|
||||
|
||||
let pseudo_code_skeleton = |theme: Arc<Theme>, seed: f32| -> AnyElement {
|
||||
let colors = theme.colors();
|
||||
let syntax = theme.syntax();
|
||||
|
||||
let keyword_color = syntax.get("keyword").color;
|
||||
let function_color = syntax.get("function").color;
|
||||
let string_color = syntax.get("string").color;
|
||||
let comment_color = syntax.get("comment").color;
|
||||
let variable_color = syntax.get("variable").color;
|
||||
let type_color = syntax.get("type").color;
|
||||
let punctuation_color = syntax.get("punctuation").color;
|
||||
|
||||
let syntax_colors = [
|
||||
keyword_color,
|
||||
function_color,
|
||||
string_color,
|
||||
variable_color,
|
||||
type_color,
|
||||
punctuation_color,
|
||||
comment_color,
|
||||
];
|
||||
|
||||
let line_width = |line_idx: usize, block_idx: usize| -> f32 {
|
||||
let val = (seed * 100.0 + line_idx as f32 * 20.0 + block_idx as f32 * 5.0).sin()
|
||||
* 0.5
|
||||
+ 0.5;
|
||||
0.05 + val * 0.2
|
||||
};
|
||||
|
||||
let indentation = |line_idx: usize| -> f32 {
|
||||
let step = line_idx % 6;
|
||||
if step < 3 {
|
||||
step as f32 * 0.1
|
||||
} else {
|
||||
(5 - step) as f32 * 0.1
|
||||
}
|
||||
};
|
||||
|
||||
let pick_color = |line_idx: usize, block_idx: usize| -> Hsla {
|
||||
let idx = ((seed * 10.0 + line_idx as f32 * 7.0 + block_idx as f32 * 3.0).sin()
|
||||
* 3.5)
|
||||
.abs() as usize
|
||||
% syntax_colors.len();
|
||||
syntax_colors[idx].unwrap_or(colors.text)
|
||||
};
|
||||
|
||||
let line_count = 13;
|
||||
|
||||
let lines = (0..line_count)
|
||||
.map(|line_idx| {
|
||||
let block_count = (((seed * 30.0 + line_idx as f32 * 12.0).sin() * 0.5 + 0.5)
|
||||
* 3.0)
|
||||
.round() as usize
|
||||
+ 2;
|
||||
|
||||
let indent = indentation(line_idx);
|
||||
|
||||
let blocks = (0..block_count)
|
||||
.map(|block_idx| {
|
||||
let width = line_width(line_idx, block_idx);
|
||||
let color = pick_color(line_idx, block_idx);
|
||||
item_skeleton(relative(width).into(), skeleton_height, color)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
h_flex().gap(px(2.)).ml(relative(indent)).children(blocks)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.p_1()
|
||||
.gap(px(6.))
|
||||
.children(lines)
|
||||
.into_any_element()
|
||||
};
|
||||
|
||||
let pane = div()
|
||||
.h_full()
|
||||
.flex_grow()
|
||||
.flex()
|
||||
.flex_col()
|
||||
// .child(
|
||||
// div()
|
||||
// .w_full()
|
||||
// .border_color(color.border)
|
||||
// .border_b(px(1.))
|
||||
// .h(relative(0.1))
|
||||
// .bg(color.tab_bar_background),
|
||||
// )
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.bg(color.editor_background)
|
||||
.p_2()
|
||||
.child(pseudo_code_skeleton(self.theme.clone(), self.seed)),
|
||||
);
|
||||
|
||||
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)
|
||||
.border(root_border)
|
||||
.border_color(color.border_transparent)
|
||||
.when(self.selected, |this| {
|
||||
this.border_color(color.border_selected)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.rounded(inner_radius)
|
||||
.border(child_border)
|
||||
.border_color(color.border)
|
||||
.bg(color.background)
|
||||
.child(content),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(|(_, 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(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue