Merge branch 'main' into divs

This commit is contained in:
Nathan Sobo
2023-08-22 16:35:56 -06:00
277 changed files with 19044 additions and 8896 deletions
+344
View File
@@ -0,0 +1,344 @@
use gpui::elements::StyleableComponent;
use crate::{Interactive, Toggleable};
use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle};
pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
pub trait ComponentExt<C: StyleableComponent> {
fn toggleable(self, active: bool) -> Toggle<C, ()>;
}
impl<C: StyleableComponent> ComponentExt<C> for C {
fn toggleable(self, active: bool) -> Toggle<C, ()> {
Toggle::new(self, active)
}
}
pub mod toggle {
use gpui::elements::{GeneralComponent, StyleableComponent};
use crate::Toggleable;
pub struct Toggle<C, S> {
style: S,
active: bool,
component: C,
}
impl<C: StyleableComponent> Toggle<C, ()> {
pub fn new(component: C, active: bool) -> Self {
Toggle {
active,
component,
style: (),
}
}
}
impl<C: StyleableComponent> StyleableComponent for Toggle<C, ()> {
type Style = Toggleable<C::Style>;
type Output = Toggle<C, Self::Style>;
fn with_style(self, style: Self::Style) -> Self::Output {
Toggle {
active: self.active,
component: self.component,
style,
}
}
}
impl<C: StyleableComponent> GeneralComponent for Toggle<C, Toggleable<C::Style>> {
fn render<V: gpui::View>(
self,
v: &mut V,
cx: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
self.component
.with_style(self.style.in_state(self.active).clone())
.render(v, cx)
}
}
}
pub mod action_button {
use std::borrow::Cow;
use gpui::{
elements::{
ContainerStyle, GeneralComponent, MouseEventHandler, StyleableComponent, TooltipStyle,
},
platform::{CursorStyle, MouseButton},
Action, Element, TypeTag, View,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
use crate::Interactive;
pub struct ActionButton<C, S> {
action: Box<dyn Action>,
tooltip: Cow<'static, str>,
tooltip_style: TooltipStyle,
tag: TypeTag,
contents: C,
style: Interactive<S>,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ButtonStyle<C> {
#[serde(flatten)]
container: ContainerStyle,
button_width: Option<f32>,
button_height: Option<f32>,
#[serde(flatten)]
contents: C,
}
impl ActionButton<(), ()> {
pub fn new_dynamic(
action: Box<dyn Action>,
tooltip: impl Into<Cow<'static, str>>,
tooltip_style: TooltipStyle,
) -> Self {
Self {
contents: (),
tag: action.type_tag(),
style: Interactive::new_blank(),
tooltip: tooltip.into(),
tooltip_style,
action,
}
}
pub fn new<A: Action + Clone>(
action: A,
tooltip: impl Into<Cow<'static, str>>,
tooltip_style: TooltipStyle,
) -> Self {
Self::new_dynamic(Box::new(action), tooltip, tooltip_style)
}
pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
ActionButton {
action: self.action,
tag: self.tag,
style: self.style,
tooltip: self.tooltip,
tooltip_style: self.tooltip_style,
contents,
}
}
}
impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
type Style = Interactive<ButtonStyle<C::Style>>;
type Output = ActionButton<C, ButtonStyle<C::Style>>;
fn with_style(self, style: Self::Style) -> Self::Output {
ActionButton {
action: self.action,
tag: self.tag,
contents: self.contents,
tooltip: self.tooltip,
tooltip_style: self.tooltip_style,
style,
}
}
}
impl<C: StyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
let style = self.style.style_for(state);
let mut contents = self
.contents
.with_style(style.contents.to_owned())
.render(v, cx)
.contained()
.with_style(style.container)
.constrained();
if let Some(height) = style.button_height {
contents = contents.with_height(height);
}
if let Some(width) = style.button_width {
contents = contents.with_width(width);
}
contents.into_any()
})
.on_click(MouseButton::Left, {
let action = self.action.boxed_clone();
move |_, _, cx| {
let window = cx.window();
let view = cx.view_id();
let action = action.boxed_clone();
cx.spawn(|_, mut cx| async move {
window.dispatch_action(view, action.as_ref(), &mut cx)
})
.detach();
}
})
.with_cursor_style(CursorStyle::PointingHand)
.with_dynamic_tooltip(
self.tag,
0,
self.tooltip,
Some(self.action),
self.tooltip_style,
cx,
)
.into_any()
}
}
}
pub mod svg {
use std::borrow::Cow;
use gpui::{
elements::{GeneralComponent, StyleableComponent},
Element,
};
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Clone, Default, JsonSchema)]
pub struct SvgStyle {
icon_width: f32,
icon_height: f32,
color: gpui::color::Color,
}
impl<'de> Deserialize<'de> for SvgStyle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
pub enum IconSize {
IconSize { icon_size: f32 },
Dimensions { width: f32, height: f32 },
}
#[derive(Deserialize)]
struct SvgStyleHelper {
#[serde(flatten)]
size: IconSize,
color: gpui::color::Color,
}
let json = SvgStyleHelper::deserialize(deserializer)?;
let color = json.color;
let result = match json.size {
IconSize::IconSize { icon_size } => SvgStyle {
icon_width: icon_size,
icon_height: icon_size,
color,
},
IconSize::Dimensions { width, height } => SvgStyle {
icon_width: width,
icon_height: height,
color,
},
};
Ok(result)
}
}
pub struct Svg<S> {
path: Cow<'static, str>,
style: S,
}
impl Svg<()> {
pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
Self {
path: path.into(),
style: (),
}
}
}
impl StyleableComponent for Svg<()> {
type Style = SvgStyle;
type Output = Svg<SvgStyle>;
fn with_style(self, style: Self::Style) -> Self::Output {
Svg {
path: self.path,
style,
}
}
}
impl GeneralComponent for Svg<SvgStyle> {
fn render<V: gpui::View>(
self,
_: &mut V,
_: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
gpui::elements::Svg::new(self.path)
.with_color(self.style.color)
.constrained()
.with_width(self.style.icon_width)
.with_height(self.style.icon_height)
.into_any()
}
}
}
pub mod label {
use std::borrow::Cow;
use gpui::{
elements::{GeneralComponent, LabelStyle, StyleableComponent},
Element,
};
pub struct Label<S> {
text: Cow<'static, str>,
style: S,
}
impl Label<()> {
pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
Self {
text: text.into(),
style: (),
}
}
}
impl StyleableComponent for Label<()> {
type Style = LabelStyle;
type Output = Label<LabelStyle>;
fn with_style(self, style: Self::Style) -> Self::Output {
Label {
text: self.text,
style,
}
}
}
impl GeneralComponent for Label<LabelStyle> {
fn render<V: gpui::View>(
self,
_: &mut V,
_: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
gpui::elements::Label::new(self.text, self.style).into_any()
}
}
}
+108 -21
View File
@@ -1,7 +1,9 @@
pub mod components;
mod theme_registry;
mod theme_settings;
pub mod ui;
use components::ToggleIconButtonStyle;
use gpui::{
color::Color,
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
@@ -13,7 +15,7 @@ use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use settings::SettingsStore;
use std::{collections::HashMap, sync::Arc};
use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle};
use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle};
pub use theme_registry::*;
pub use theme_settings::*;
@@ -43,11 +45,9 @@ pub struct Theme {
pub meta: ThemeMeta,
pub workspace: Workspace,
pub context_menu: ContextMenu,
pub contacts_popover: ContactsPopover,
pub contact_list: ContactList,
pub toolbar_dropdown_menu: DropdownMenu,
pub copilot: Copilot,
pub contact_finder: ContactFinder,
pub collab_panel: CollabPanel,
pub project_panel: ProjectPanel,
pub command_palette: CommandPalette,
pub picker: Picker,
@@ -117,6 +117,7 @@ pub struct Titlebar {
#[serde(flatten)]
pub container: ContainerStyle,
pub height: f32,
pub menu: TitlebarMenu,
pub project_menu_button: Toggleable<Interactive<ContainedText>>,
pub project_name_divider: ContainedText,
pub git_menu_button: Toggleable<Interactive<ContainedText>>,
@@ -143,6 +144,12 @@ pub struct Titlebar {
pub user_menu: UserMenu,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct TitlebarMenu {
pub width: f32,
pub height: f32,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct UserMenu {
pub user_menu_button_online: UserMenuButton,
@@ -177,7 +184,7 @@ pub struct CopilotAuth {
pub prompting: CopilotAuthPrompting,
pub not_authorized: CopilotAuthNotAuthorized,
pub authorized: CopilotAuthAuthorized,
pub cta_button: ButtonStyle,
pub cta_button: CopilotCTAButton,
pub header: IconStyle,
}
@@ -191,7 +198,7 @@ pub struct CopilotAuthPrompting {
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct DeviceCode {
pub text: TextStyle,
pub cta: ButtonStyle,
pub cta: CopilotCTAButton,
pub left: f32,
pub left_container: ContainerStyle,
pub right: f32,
@@ -211,33 +218,69 @@ pub struct CopilotAuthAuthorized {
}
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactsPopover {
pub struct CollabPanel {
#[serde(flatten)]
pub container: ContainerStyle,
pub height: f32,
pub width: f32,
}
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactList {
pub list_empty_state: Toggleable<Interactive<ContainedText>>,
pub list_empty_icon: Icon,
pub list_empty_label_container: ContainerStyle,
pub log_in_button: Interactive<ContainedText>,
pub channel_editor: ContainerStyle,
pub channel_hash: Icon,
pub tabbed_modal: TabbedModal,
pub contact_finder: ContactFinder,
pub channel_modal: ChannelModal,
pub user_query_editor: FieldEditor,
pub user_query_editor_height: f32,
pub add_contact_button: IconButton,
pub header_row: Toggleable<Interactive<ContainedText>>,
pub leave_call_button: Toggleable<Interactive<IconButton>>,
pub add_contact_button: Toggleable<Interactive<IconButton>>,
pub add_channel_button: Toggleable<Interactive<IconButton>>,
pub header_row: ContainedText,
pub subheader_row: Toggleable<Interactive<ContainedText>>,
pub leave_call: Interactive<ContainedText>,
pub contact_row: Toggleable<Interactive<ContainerStyle>>,
pub channel_row: Toggleable<Interactive<ContainerStyle>>,
pub channel_name: ContainedText,
pub row_height: f32,
pub project_row: Toggleable<Interactive<ProjectRow>>,
pub tree_branch: Toggleable<Interactive<TreeBranch>>,
pub contact_avatar: ImageStyle,
pub channel_avatar: ImageStyle,
pub extra_participant_label: ContainedText,
pub contact_status_free: ContainerStyle,
pub contact_status_busy: ContainerStyle,
pub contact_username: ContainedText,
pub contact_button: Interactive<IconButton>,
pub contact_button_spacing: f32,
pub channel_indent: f32,
pub disabled_button: IconButton,
pub section_icon_size: f32,
pub calling_indicator: ContainedText,
pub face_overlap: f32,
}
#[derive(Deserialize, Default, JsonSchema)]
pub struct TabbedModal {
pub tab_button: Toggleable<Interactive<ContainedText>>,
pub modal: ContainerStyle,
pub header: ContainerStyle,
pub body: ContainerStyle,
pub title: ContainedText,
pub picker: Picker,
pub max_height: f32,
pub max_width: f32,
pub row_height: f32,
}
#[derive(Deserialize, Default, JsonSchema)]
pub struct ChannelModal {
pub contact_avatar: ImageStyle,
pub contact_username: ContainerStyle,
pub remove_member_button: ContainedText,
pub cancel_invite_button: ContainedText,
pub member_icon: IconButton,
pub invitee_icon: IconButton,
pub member_tag: ContainedText,
}
#[derive(Deserialize, Default, JsonSchema)]
@@ -256,8 +299,6 @@ pub struct TreeBranch {
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactFinder {
pub picker: Picker,
pub row_height: f32,
pub contact_avatar: ImageStyle,
pub contact_username: ContainerStyle,
pub contact_button: IconButton,
@@ -295,6 +336,7 @@ pub struct TabBar {
pub inactive_pane: TabStyles,
pub dragged_tab: Tab,
pub height: f32,
pub nav_button: Interactive<IconButton>,
}
impl TabBar {
@@ -359,7 +401,7 @@ pub struct Toolbar {
pub container: ContainerStyle,
pub height: f32,
pub item_spacing: f32,
pub nav_button: Interactive<IconButton>,
pub toggleable_tool: Toggleable<Interactive<IconButton>>,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
@@ -379,12 +421,20 @@ pub struct Search {
pub include_exclude_editor: FindEditor,
pub invalid_include_exclude_editor: ContainerStyle,
pub include_exclude_inputs: ContainedText,
pub option_button: Toggleable<Interactive<ContainedText>>,
pub action_button: Interactive<ContainedText>,
pub option_button: Toggleable<Interactive<IconButton>>,
pub option_button_component: ToggleIconButtonStyle,
pub action_button: Toggleable<Interactive<ContainedText>>,
pub match_background: Color,
pub match_index: ContainedText,
pub results_status: TextStyle,
pub major_results_status: TextStyle,
pub minor_results_status: TextStyle,
pub dismiss_button: Interactive<IconButton>,
pub editor_icon: IconStyle,
pub mode_button: Toggleable<Interactive<ContainedText>>,
pub nav_button: Toggleable<Interactive<ContainedLabel>>,
pub search_bar_row_height: f32,
pub option_button_height: f32,
pub modes_container: ContainerStyle,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
@@ -840,12 +890,32 @@ pub struct Interactive<T> {
pub disabled: Option<T>,
}
impl Interactive<()> {
pub fn new_blank() -> Self {
Self {
default: (),
hovered: None,
clicked: None,
disabled: None,
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
pub struct Toggleable<T> {
active: T,
inactive: T,
}
impl Toggleable<()> {
pub fn new_blank() -> Self {
Self {
active: (),
inactive: (),
}
}
}
impl<T> Toggleable<T> {
pub fn new(active: T, inactive: T) -> Self {
Self { active, inactive }
@@ -860,6 +930,7 @@ impl<T> Toggleable<T> {
pub fn active_state(&self) -> &T {
self.in_state(true)
}
pub fn inactive_state(&self) -> &T {
self.in_state(false)
}
@@ -880,6 +951,16 @@ impl<T> Interactive<T> {
}
}
impl<T> Toggleable<Interactive<T>> {
pub fn style_for(&self, active: bool, state: &mut MouseState) -> &T {
self.in_state(active).style_for(state)
}
pub fn default_style(&self) -> &T {
&self.inactive.default
}
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -1045,6 +1126,12 @@ pub struct AssistantStyle {
pub saved_conversation: SavedConversation,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Contained<T> {
container: ContainerStyle,
contained: T,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct SavedConversation {
pub container: Interactive<ContainerStyle>,
+19 -9
View File
@@ -34,7 +34,7 @@ pub fn checkbox<Tag, V, F>(
id: usize,
cx: &mut ViewContext<V>,
change: F,
) -> MouseEventHandler<Tag, V>
) -> MouseEventHandler<V>
where
Tag: 'static,
V: 'static,
@@ -43,7 +43,7 @@ where
let label = Label::new(label, style.label.text.clone())
.contained()
.with_style(style.label.container);
checkbox_with_label(label, style, checked, id, cx, change)
checkbox_with_label::<Tag, _, _, _>(label, style, checked, id, cx, change)
}
pub fn checkbox_with_label<Tag, D, V, F>(
@@ -53,14 +53,14 @@ pub fn checkbox_with_label<Tag, D, V, F>(
id: usize,
cx: &mut ViewContext<V>,
change: F,
) -> MouseEventHandler<Tag, V>
) -> MouseEventHandler<V>
where
Tag: 'static,
D: Element<V>,
V: 'static,
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
{
MouseEventHandler::new(id, cx, |state, _| {
MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
let indicator = if checked {
svg(&style.icon)
} else {
@@ -107,6 +107,16 @@ pub struct IconStyle {
pub container: ContainerStyle,
}
impl IconStyle {
pub fn width(&self) -> f32 {
self.icon.dimensions.width
+ self.container.padding.left
+ self.container.padding.right
+ self.container.margin.left
+ self.container.margin.right
}
}
pub fn icon<V: 'static>(style: &IconStyle) -> Container<V> {
svg(&style.icon).contained().with_style(style.container)
}
@@ -135,22 +145,22 @@ pub fn keystroke_label<V: 'static>(
.with_style(label_style.container)
}
pub type ButtonStyle = Interactive<ContainedText>;
pub type CopilotCTAButton = Interactive<ContainedText>;
pub fn cta_button<Tag, L, V, F>(
label: L,
max_width: f32,
style: &ButtonStyle,
style: &CopilotCTAButton,
cx: &mut ViewContext<V>,
f: F,
) -> MouseEventHandler<Tag, V>
) -> MouseEventHandler<V>
where
Tag: 'static,
L: Into<Cow<'static, str>>,
V: 'static,
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
let style = style.style_for(state);
Label::new(label, style.text.to_owned())
.aligned()
@@ -205,7 +215,7 @@ where
))
.with_child(
// FIXME: Get a better tag type
MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
let style = style.close_icon.style_for(state);
icon(style)
})