diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 6c900f5a07..708fb89e86 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -113,7 +113,6 @@ impl View for CollabTitlebarItem { .with_child( right_container.contained().with_background_color( theme - .workspace .titlebar .container .background_color @@ -200,11 +199,11 @@ impl CollabTitlebarItem { .as_ref() .and_then(RepositoryEntry::branch) .map(|branch| format!("/{branch}")); - let text_style = theme.workspace.titlebar.title.clone(); - let item_spacing = theme.workspace.titlebar.item_spacing; + let text_style = theme.titlebar.title.clone(); + let item_spacing = theme.titlebar.item_spacing; let mut highlight = text_style.clone(); - highlight.color = theme.workspace.titlebar.highlight_color; + highlight.color = theme.titlebar.highlight_color; let style = LabelStyle { text: text_style, @@ -325,7 +324,7 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut ViewContext, ) -> AnyElement { - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; let badge = if self .user_store @@ -410,7 +409,7 @@ impl CollabTitlebarItem { } let active = room.read(cx).is_screen_sharing(); - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; MouseEventHandler::::new(0, cx, |state, _| { let style = titlebar .screen_share_button @@ -459,7 +458,7 @@ impl CollabTitlebarItem { tooltip = "Mute microphone\nRight click for options"; } - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; MouseEventHandler::::new(0, cx, |state, _| { let style = titlebar .toggle_microphone_button @@ -512,7 +511,7 @@ impl CollabTitlebarItem { tooltip = "Mute speakers\nRight click for options"; } - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; MouseEventHandler::::new(0, cx, |state, _| { let style = titlebar .toggle_speakers_button @@ -547,7 +546,7 @@ impl CollabTitlebarItem { let icon = "icons/radix/exit.svg"; let tooltip = "Leave call"; - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; MouseEventHandler::::new(0, cx, |state, _| { let style = titlebar.leave_call_button.style_for(state); Svg::new(icon) @@ -596,7 +595,7 @@ impl CollabTitlebarItem { "Share project with call participants" }; - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; enum ShareUnshare {} Some( @@ -627,7 +626,7 @@ impl CollabTitlebarItem { ) .aligned() .contained() - .with_margin_left(theme.workspace.titlebar.item_spacing) + .with_margin_left(theme.titlebar.item_spacing) .into_any(), ) } @@ -640,9 +639,9 @@ impl CollabTitlebarItem { ) -> AnyElement { let tooltip = theme.tooltip.clone(); let user_menu_button_style = if avatar.is_some() { - &theme.titlebar.user_menu_button_online + &theme.titlebar.user_menu.user_menu_button_online } else { - &theme.titlebar.user_menu_button_offline + &theme.titlebar.user_menu.user_menu_button_offline }; let avatar_style = &user_menu_button_style.avatar; @@ -703,7 +702,7 @@ impl CollabTitlebarItem { } fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { - let titlebar = &theme.workspace.titlebar; + let titlebar = &theme.titlebar; MouseEventHandler::::new(0, cx, |state, _| { let style = titlebar.sign_in_button.inactive_state().style_for(state); Label::new("Sign In", style.text.clone()) @@ -771,7 +770,7 @@ impl CollabTitlebarItem { theme, cx, )) - .with_margin_right(theme.workspace.titlebar.face_pile_spacing), + .with_margin_right(theme.titlebar.face_pile_spacing), ) }) .collect() @@ -795,7 +794,7 @@ impl CollabTitlebarItem { theme, cx, )) - .with_margin_right(theme.workspace.titlebar.item_spacing) + .with_margin_right(theme.titlebar.item_spacing) .into_any() } @@ -827,11 +826,10 @@ impl CollabTitlebarItem { }) .unwrap_or(false); - let leader_style = theme.workspace.titlebar.leader_avatar; - let follower_style = theme.workspace.titlebar.follower_avatar; + let leader_style = theme.titlebar.leader_avatar; + let follower_style = theme.titlebar.follower_avatar; let mut background_color = theme - .workspace .titlebar .container .background_color @@ -846,7 +844,7 @@ impl CollabTitlebarItem { let mut content = Stack::new() .with_children(user.avatar.as_ref().map(|avatar| { - let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap) + let face_pile = FacePile::new(theme.titlebar.follower_avatar_overlap) .with_child(Self::render_face( avatar.clone(), Self::location_style(workspace, location, leader_style, cx), @@ -891,7 +889,7 @@ impl CollabTitlebarItem { let mut container = face_pile .contained() - .with_style(theme.workspace.titlebar.leader_selection); + .with_style(theme.titlebar.leader_selection); if let Some(replica_id) = replica_id { if followed_by_self { @@ -908,8 +906,8 @@ impl CollabTitlebarItem { Some( AvatarRibbon::new(color) .constrained() - .with_width(theme.workspace.titlebar.avatar_ribbon.width) - .with_height(theme.workspace.titlebar.avatar_ribbon.height) + .with_width(theme.titlebar.avatar_ribbon.width) + .with_height(theme.titlebar.avatar_ribbon.height) .aligned() .bottom(), ) @@ -1029,22 +1027,22 @@ impl CollabTitlebarItem { | client::Status::Reconnecting { .. } | client::Status::ReconnectionError { .. } => Some( Svg::new("icons/cloud_slash_12.svg") - .with_color(theme.workspace.titlebar.offline_icon.color) + .with_color(theme.titlebar.offline_icon.color) .constrained() - .with_width(theme.workspace.titlebar.offline_icon.width) + .with_width(theme.titlebar.offline_icon.width) .aligned() .contained() - .with_style(theme.workspace.titlebar.offline_icon.container) + .with_style(theme.titlebar.offline_icon.container) .into_any(), ), client::Status::UpgradeRequired => Some( MouseEventHandler::::new(0, cx, |_, _| { Label::new( "Please update Zed to collaborate", - theme.workspace.titlebar.outdated_warning.text.clone(), + theme.titlebar.outdated_warning.text.clone(), ) .contained() - .with_style(theme.workspace.titlebar.outdated_warning.container) + .with_style(theme.titlebar.outdated_warning.container) .aligned() }) .with_cursor_style(CursorStyle::PointingHand) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 4d7f334086..06c6026e3a 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -66,7 +66,7 @@ pub struct Theme { pub feedback: FeedbackStyle, pub welcome: WelcomeStyle, pub color_scheme: ColorScheme, - pub titlebar: UserMenu, + pub titlebar: Titlebar, } #[derive(Deserialize, Default, Clone, JsonSchema)] @@ -81,7 +81,6 @@ pub struct ThemeMeta { pub struct Workspace { pub background: Color, pub blank_pane: BlankPaneStyle, - pub titlebar: Titlebar, pub tab_bar: TabBar, pub pane_divider: Border, pub leader_border_opacity: f32, @@ -138,8 +137,8 @@ pub struct Titlebar { pub toggle_microphone_button: Toggleable>, pub toggle_speakers_button: Toggleable>, pub leave_call_button: Interactive, - pub user_menu_button: Toggleable>, pub toggle_contacts_badge: ContainerStyle, + pub user_menu: UserMenu, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index dfc2f4f795..066ea5f8a6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2296,11 +2296,11 @@ impl Workspace { // (https://github.com/zed-industries/zed/issues/1290) let is_fullscreen = cx.window_is_fullscreen(); let container_theme = if is_fullscreen { - let mut container_theme = theme.workspace.titlebar.container; + let mut container_theme = theme.titlebar.container; container_theme.padding.left = container_theme.padding.right; container_theme } else { - theme.workspace.titlebar.container + theme.titlebar.container }; enum TitleBar {} @@ -2320,7 +2320,7 @@ impl Workspace { } }) .constrained() - .with_height(theme.workspace.titlebar.height) + .with_height(theme.titlebar.height) .into_any_named("titlebar") } diff --git a/styles/src/styleTree/titlebar.ts b/styles/src/styleTree/titlebar.ts index 881600d76c..5f0fc3f528 100644 --- a/styles/src/styleTree/titlebar.ts +++ b/styles/src/styleTree/titlebar.ts @@ -1,6 +1,49 @@ import { ColorScheme } from "../common"; +import { icon_button, toggleable_icon_button } from "../component/icon_button" +import { toggleable_text_button } from "../component/text_button" import { interactive, toggleable } from "../element" -import { background, foreground, text } from "./components"; +import { withOpacity } from "../theme/color"; +import { background, border, foreground, text } from "./components"; + +const ITEM_SPACING = 8 + +interface SpacingProps { + container_height: number; + spacing: number; +} + +function build_spacing( + container_height: number, + element_height: number, + spacing: number +) { + return { + group: spacing * 2, + item: spacing / 2, + marginY: (container_height - element_height) / 2, + marginX: (container_height - element_height) / 2, + } +} + +function mac_os_controls(theme: ColorScheme, { container_height, spacing }: SpacingProps) { + return {} +} + +function project_info(theme: ColorScheme, { container_height, spacing }: SpacingProps) { + return {} +} + +function collaboration_stacks(theme: ColorScheme, { container_height, spacing }: SpacingProps) { + return {} +} + +function sharing_controls(theme: ColorScheme, { container_height, spacing }: SpacingProps) { + return {} +} + +function call_controls(theme: ColorScheme, { container_height, spacing }: SpacingProps) { + return {} +} const titlebarButton = (theme: ColorScheme) => toggleable({ base: interactive({ @@ -57,29 +100,210 @@ const titlebarButton = (theme: ColorScheme) => toggleable({ * When logged out only shows a chevron. */ function userMenuButton(theme: ColorScheme, online: boolean) { + const button = toggleable({ + base: interactive({ + base: { + cornerRadius: 6, + height: 19, + width: online ? 36 : 23, + padding: { + top: 2, + bottom: 2, + left: 6, + right: 6, + }, + ...text(theme.lowest, "sans", { size: "xs" }), + background: background(theme.lowest), + }, + state: { + hovered: { + ...text(theme.lowest, "sans", "hovered", { + size: "xs", + }), + background: background(theme.lowest, "hovered"), + }, + clicked: { + ...text(theme.lowest, "sans", "pressed", { + size: "xs", + }), + background: background(theme.lowest, "pressed"), + }, + }, + }), + state: { + active: { + default: { + ...text(theme.lowest, "sans", "active", { size: "xs" }), + background: background(theme.middle), + }, + hovered: { + ...text(theme.lowest, "sans", "active", { size: "xs" }), + background: background(theme.middle, "hovered"), + }, + clicked: { + ...text(theme.lowest, "sans", "active", { size: "xs" }), + background: background(theme.middle, "pressed"), + }, + }, + } + }); + return { - user_menu: titlebarButton(theme), + user_menu: button, avatar: { icon_width: 16, icon_height: 16, cornerRadius: 4, outer_corner_radius: 0, outer_width: 0, - outerWidth: 10, - outerCornerRadius: 10 + outerWidth: 16, + outerCornerRadius: 16 }, icon: { + margin: { + left: online ? 2 : 0, + }, width: 11, height: 11, - color: online ? foreground(theme.lowest) : background(theme.lowest) + color: foreground(theme.lowest) } } } export function titlebar(theme: ColorScheme) { - return { - userMenuButtonOnline: userMenuButton(theme, true), - userMenuButtonOffline: userMenuButton(theme, false) + const avatarWidth = 18 + const avatarOuterWidth = avatarWidth + 4 + const followerAvatarWidth = 14 + const followerAvatarOuterWidth = followerAvatarWidth + 4 + return { + ITEM_SPACING, + facePileSpacing: 2, + height: 33, // 32px + 1px border. It's important the content area of the titlebar is evenly sized to vertically center avatar images. + background: background(theme.lowest), + border: border(theme.lowest, { bottom: true }), + padding: { + left: 80, + right: ITEM_SPACING, + }, + + // Project + title: text(theme.lowest, "sans", "variant"), + highlight_color: text(theme.lowest, "sans", "active").color, + + // Collaborators + leaderAvatar: { + width: avatarWidth, + outerWidth: avatarOuterWidth, + cornerRadius: avatarWidth / 2, + outerCornerRadius: avatarOuterWidth / 2, + }, + followerAvatar: { + width: followerAvatarWidth, + outerWidth: followerAvatarOuterWidth, + cornerRadius: followerAvatarWidth / 2, + outerCornerRadius: followerAvatarOuterWidth / 2, + }, + inactiveAvatarGrayscale: true, + followerAvatarOverlap: 8, + leaderSelection: { + margin: { + top: 4, + bottom: 4, + }, + padding: { + left: 2, + right: 2, + top: 2, + bottom: 2, + }, + cornerRadius: 6, + }, + avatarRibbon: { + height: 3, + width: 11, + // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded. + }, + + // Sign in buttom + sign_in_button: toggleable_text_button(theme, {}), + + // Offline Indicator + offlineIcon: { + color: foreground(theme.lowest, "variant"), + width: 16, + margin: { + left: ITEM_SPACING, + }, + padding: { + right: 4, + }, + }, + + // Notice that the collaboration server is out of date + outdatedWarning: { + ...text(theme.lowest, "sans", "warning", { size: "xs" }), + background: withOpacity(background(theme.lowest, "warning"), 0.3), + border: border(theme.lowest, "warning"), + margin: { + left: ITEM_SPACING, + }, + padding: { + left: 8, + right: 8, + }, + cornerRadius: 6, + }, + + leave_call_button: icon_button(theme, { + margin: { + left: ITEM_SPACING / 2, + right: ITEM_SPACING + }, + }), + + toggle_microphone_button: toggleable_icon_button(theme, { + margin: { + left: ITEM_SPACING, + right: ITEM_SPACING / 2 + }, + active_color: 'negative' + }), + + toggle_speakers_button: toggleable_icon_button(theme, { + margin: { + left: ITEM_SPACING / 2, + right: ITEM_SPACING / 2 + }, + }), + + screen_share_button: toggleable_icon_button(theme, { + margin: { + left: ITEM_SPACING / 2, + right: ITEM_SPACING + }, + active_color: 'accent' + }), + + toggle_contacts_button: toggleable_icon_button(theme, { + margin: { + left: ITEM_SPACING, + right: ITEM_SPACING / 2 + }, + }), + + // Jewel that notifies you that there are new contact requests + toggleContactsBadge: { + cornerRadius: 3, + padding: 2, + margin: { top: 3, left: 3 }, + border: border(theme.lowest), + background: foreground(theme.lowest, "accent"), + }, + shareButton: toggleable_text_button(theme, {}), + user_menu: { + userMenuButtonOnline: userMenuButton(theme, true), + userMenuButtonOffline: userMenuButton(theme, false), + } } } diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 4b3c5f3b51..afc2ea4d98 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -1,6 +1,5 @@ import { ColorScheme } from "../theme/colorScheme" import { withOpacity } from "../theme/color" -import { toggleable } from "../element" import { background, border, @@ -12,94 +11,11 @@ import { import statusBar from "./statusBar" import tabBar from "./tabBar" import { interactive } from "../element" -import merge from "ts-deepmerge" -import { icon_button, toggleable_icon_button } from "../component/icon_button" -import { text_button, toggleable_text_button } from "../component/text_button" + +import { titlebar } from "./titlebar" export default function workspace(colorScheme: ColorScheme) { const layer = colorScheme.lowest const isLight = colorScheme.isLight - const itemSpacing = 8 - const titlebarButton = toggleable({ - base: interactive({ - base: { - cornerRadius: 6, - padding: { - top: 1, - bottom: 1, - left: 8, - right: 8, - }, - ...text(layer, "sans", "variant", { size: "xs" }), - background: background(layer, "variant"), - border: border(layer), - }, - state: { - hovered: { - ...text(layer, "sans", "variant", "hovered", { - size: "xs", - }), - background: background(layer, "variant", "hovered"), - border: border(layer, "variant", "hovered"), - }, - clicked: { - ...text(layer, "sans", "variant", "pressed", { - size: "xs", - }), - background: background(layer, "variant", "pressed"), - border: border(layer, "variant", "pressed"), - }, - }, - }), - state: { - active: { - default: { - ...text(layer, "sans", "variant", "active", { size: "xs" }), - background: background(layer, "variant", "active"), - border: border(layer, "variant", "active"), - }, - }, - } - }); - const signInButton = toggleable({ - base: interactive({ - base: { - cornerRadius: 6, - padding: { - top: 1, - bottom: 1, - left: 8, - right: 8, - }, - ...text(layer, "sans", "variant", { size: "xs" }), - background: background(layer, "variant"), - }, - state: { - hovered: { - ...text(layer, "sans", "variant", "hovered", { size: "xs" }), - background: background(layer, "variant", "hovered"), - //border: border(layer, "variant", "hovered"), - }, - clicked: { - ...text(layer, "sans", "variant", "pressed", { size: "xs" }), - background: background(layer, "variant", "pressed"), - //border: border(layer, "variant", "pressed"), - } - } - }), - state: { - active: { - default: { - ...text(layer, "sans", "variant", "active", { size: "xs" }), - background: background(layer, "variant", "active"), - //border: border(layer, "variant", "active"), - } - }, - } - }); - const avatarWidth = 18 - const avatarOuterWidth = avatarWidth + 4 - const followerAvatarWidth = 14 - const followerAvatarOuterWidth = followerAvatarWidth + 4 return { background: background(colorScheme.lowest), @@ -209,163 +125,7 @@ export default function workspace(colorScheme: ColorScheme) { width: 1, }, statusBar: statusBar(colorScheme), - titlebar: { - itemSpacing, - facePileSpacing: 2, - height: 33, // 32px + 1px border. It's important the content area of the titlebar is evenly sized to vertically center avatar images. - background: background(layer), - border: border(layer, { bottom: true }), - padding: { - left: 80, - right: itemSpacing, - }, - - // Project - title: text(layer, "sans", "variant"), - highlight_color: text(layer, "sans", "active").color, - - // Collaborators - leaderAvatar: { - width: avatarWidth, - outerWidth: avatarOuterWidth, - cornerRadius: avatarWidth / 2, - outerCornerRadius: avatarOuterWidth / 2, - }, - followerAvatar: { - width: followerAvatarWidth, - outerWidth: followerAvatarOuterWidth, - cornerRadius: followerAvatarWidth / 2, - outerCornerRadius: followerAvatarOuterWidth / 2, - }, - inactiveAvatarGrayscale: true, - followerAvatarOverlap: 8, - leaderSelection: { - margin: { - top: 4, - bottom: 4, - }, - padding: { - left: 2, - right: 2, - top: 2, - bottom: 2, - }, - cornerRadius: 6, - }, - avatarRibbon: { - height: 3, - width: 11, - // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded. - }, - - // Sign in buttom - // FlatButton, Variant - sign_in_button: merge(titlebarButton, { - inactive: { - default: { - margin: { - left: itemSpacing, - }, - }, - }, - - signInButton, - - }), - - // Offline Indicator - offlineIcon: { - color: foreground(layer, "variant"), - width: 16, - margin: { - left: itemSpacing, - }, - padding: { - right: 4, - }, - }, - - // Notice that the collaboration server is out of date - outdatedWarning: { - ...text(layer, "sans", "warning", { size: "xs" }), - background: withOpacity(background(layer, "warning"), 0.3), - border: border(layer, "warning"), - margin: { - left: itemSpacing, - }, - padding: { - left: 8, - right: 8, - }, - cornerRadius: 6, - }, - - leave_call_button: icon_button(colorScheme, { - margin: { - left: itemSpacing / 2, - right: itemSpacing - }, - }), - - toggle_microphone_button: toggleable_icon_button(colorScheme, { - margin: { - left: itemSpacing, - right: itemSpacing / 2 - }, - active_color: 'negative' - }), - - toggle_speakers_button: toggleable_icon_button(colorScheme, { - margin: { - left: itemSpacing / 2, - right: itemSpacing / 2 - }, - }), - - screen_share_button: toggleable_icon_button(colorScheme, { - margin: { - left: itemSpacing / 2, - right: itemSpacing - }, - active_color: 'accent' - }), - - toggle_contacts_button: toggleable_icon_button(colorScheme, { - margin: { - left: itemSpacing, - right: itemSpacing / 2 - }, - }), - - user_menu_button: - merge(titlebarButton, { - inactive: { - default: { - buttonWidth: 20, - iconWidth: 12, - }, - }, - active: { - default: { - iconWidth: 12, - button_width: 20, - background: background(layer, "variant", "active"), - color: foreground(layer, "variant", "active"), - } - }, - }), - - // Jewel that notifies you that there are new contact requests - toggleContactsBadge: { - cornerRadius: 3, - padding: 2, - margin: { top: 3, left: 3 }, - border: border(layer), - background: foreground(layer, "accent"), - }, - shareButton: toggleable_text_button(colorScheme, {}), - }, - + titlebar: titlebar(colorScheme), toolbar: { height: 34, background: background(colorScheme.highest),