diff --git a/Cargo.lock b/Cargo.lock index 299ff4203b..e689388a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,6 +1095,23 @@ dependencies = [ "workspace", ] +[[package]] +name = "breadcrumbs2" +version = "0.1.0" +dependencies = [ + "collections", + "editor2", + "gpui2", + "itertools 0.10.5", + "language2", + "project2", + "search2", + "settings2", + "theme2", + "ui2", + "workspace2", +] + [[package]] name = "bromberg_sl2" version = "0.6.0" @@ -1688,7 +1705,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.29.0" +version = "0.29.1" dependencies = [ "anyhow", "async-trait", @@ -2126,6 +2143,25 @@ dependencies = [ "workspace", ] +[[package]] +name = "copilot_button2" +version = "0.1.0" +dependencies = [ + "anyhow", + "copilot2", + "editor2", + "fs2", + "futures 0.3.28", + "gpui2", + "language2", + "settings2", + "smol", + "theme2", + "util", + "workspace2", + "zed_actions2", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -4774,6 +4810,24 @@ dependencies = [ "workspace", ] +[[package]] +name = "language_selector2" +version = "0.1.0" +dependencies = [ + "anyhow", + "editor2", + "fuzzy2", + "gpui2", + "language2", + "picker2", + "project2", + "settings2", + "theme2", + "ui2", + "util", + "workspace2", +] + [[package]] name = "language_tools" version = "0.1.0" @@ -11726,6 +11780,7 @@ dependencies = [ "audio2", "auto_update2", "backtrace", + "breadcrumbs2", "call2", "channel2", "chrono", @@ -11735,6 +11790,7 @@ dependencies = [ "collections", "command_palette2", "copilot2", + "copilot_button2", "ctor", "db2", "diagnostics2", @@ -11754,6 +11810,7 @@ dependencies = [ "isahc", "journal2", "language2", + "language_selector2", "lazy_static", "libc", "log", diff --git a/Cargo.toml b/Cargo.toml index 5dc30ca40b..0a7e4aa18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "crates/auto_update", "crates/auto_update2", "crates/breadcrumbs", + "crates/breadcrumbs2", "crates/call", "crates/call2", "crates/channel", @@ -60,6 +61,7 @@ members = [ "crates/language", "crates/language2", "crates/language_selector", + "crates/language_selector2", "crates/language_tools", "crates/live_kit_client", "crates/live_kit_server", diff --git a/assets/icons/copy.svg b/assets/icons/copy.svg new file mode 100644 index 0000000000..8b755e8063 --- /dev/null +++ b/assets/icons/copy.svg @@ -0,0 +1 @@ + diff --git a/crates/auto_update2/src/auto_update.rs b/crates/auto_update2/src/auto_update.rs index d2eab15d09..72dbe32b5a 100644 --- a/crates/auto_update2/src/auto_update.rs +++ b/crates/auto_update2/src/auto_update.rs @@ -102,7 +102,7 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo }) .detach(); - if let Some(version) = *ZED_APP_VERSION { + if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) { let auto_updater = cx.build_model(|cx| { let updater = AutoUpdater::new(version, http_client, server_url); diff --git a/crates/breadcrumbs2/Cargo.toml b/crates/breadcrumbs2/Cargo.toml new file mode 100644 index 0000000000..8555afe980 --- /dev/null +++ b/crates/breadcrumbs2/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "breadcrumbs2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/breadcrumbs.rs" +doctest = false + +[dependencies] +collections = { path = "../collections" } +editor = { package = "editor2", path = "../editor2" } +gpui = { package = "gpui2", path = "../gpui2" } +ui = { package = "ui2", path = "../ui2" } +language = { package = "language2", path = "../language2" } +project = { package = "project2", path = "../project2" } +search = { package = "search2", path = "../search2" } +settings = { package = "settings2", path = "../settings2" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package = "workspace2", path = "../workspace2" } +# outline = { path = "../outline" } +itertools = "0.10" + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] } diff --git a/crates/breadcrumbs2/src/breadcrumbs.rs b/crates/breadcrumbs2/src/breadcrumbs.rs new file mode 100644 index 0000000000..75195a3159 --- /dev/null +++ b/crates/breadcrumbs2/src/breadcrumbs.rs @@ -0,0 +1,204 @@ +use gpui::{ + Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription, + ViewContext, WeakView, +}; +use itertools::Itertools; +use theme::ActiveTheme; +use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label}; +use workspace::{ + item::{ItemEvent, ItemHandle}, + ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, +}; + +pub enum Event { + UpdateLocation, +} + +pub struct Breadcrumbs { + pane_focused: bool, + active_item: Option>, + subscription: Option, + _workspace: WeakView, +} + +impl Breadcrumbs { + pub fn new(workspace: &Workspace) -> Self { + Self { + pane_focused: false, + active_item: Default::default(), + subscription: Default::default(), + _workspace: workspace.weak_handle(), + } + } +} + +impl EventEmitter for Breadcrumbs {} +impl EventEmitter for Breadcrumbs {} + +impl Render for Breadcrumbs { + type Element = Component; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let button = ButtonLike::new("breadcrumbs") + .style(ButtonStyle::Transparent) + .disabled(true); + + let active_item = match &self.active_item { + Some(active_item) => active_item, + None => return button.into_element(), + }; + let not_editor = active_item.downcast::().is_none(); + + let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) { + Some(breadcrumbs) => breadcrumbs, + None => return button.into_element(), + } + .into_iter() + .map(|breadcrumb| { + StyledText::new(breadcrumb.text) + .with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default()) + .into_any() + }); + + let button = button.children(Itertools::intersperse_with(breadcrumbs, || { + Label::new(" › ").into_any_element() + })); + + if not_editor || !self.pane_focused { + return button.into_element(); + } + + // let this = cx.view().downgrade(); + button + .style(ButtonStyle::Filled) + .disabled(false) + .on_click(move |_, _cx| { + todo!("outline::toggle"); + // this.update(cx, |this, cx| { + // if let Some(workspace) = this.workspace.upgrade() { + // workspace.update(cx, |_workspace, _cx| { + // outline::toggle(workspace, &Default::default(), cx) + // }) + // } + // }) + // .ok(); + }) + .into_element() + } +} + +// impl View for Breadcrumbs { +// fn ui_name() -> &'static str { +// "Breadcrumbs" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let active_item = match &self.active_item { +// Some(active_item) => active_item, +// None => return Empty::new().into_any(), +// }; +// let not_editor = active_item.downcast::().is_none(); + +// let theme = theme::current(cx).clone(); +// let style = &theme.workspace.toolbar.breadcrumbs; + +// let breadcrumbs = match active_item.breadcrumbs(&theme, cx) { +// Some(breadcrumbs) => breadcrumbs, +// None => return Empty::new().into_any(), +// } +// .into_iter() +// .map(|breadcrumb| { +// Text::new( +// breadcrumb.text, +// theme.workspace.toolbar.breadcrumbs.default.text.clone(), +// ) +// .with_highlights(breadcrumb.highlights.unwrap_or_default()) +// .into_any() +// }); + +// let crumbs = Flex::row() +// .with_children(Itertools::intersperse_with(breadcrumbs, || { +// Label::new(" › ", style.default.text.clone()).into_any() +// })) +// .constrained() +// .with_height(theme.workspace.toolbar.breadcrumb_height) +// .contained(); + +// if not_editor || !self.pane_focused { +// return crumbs +// .with_style(style.default.container) +// .aligned() +// .left() +// .into_any(); +// } + +// MouseEventHandler::new::(0, cx, |state, _| { +// let style = style.style_for(state); +// crumbs.with_style(style.container) +// }) +// .on_click(MouseButton::Left, |_, this, cx| { +// if let Some(workspace) = this.workspace.upgrade(cx) { +// workspace.update(cx, |workspace, cx| { +// outline::toggle(workspace, &Default::default(), cx) +// }) +// } +// }) +// .with_tooltip::( +// 0, +// "Show symbol outline".to_owned(), +// Some(Box::new(outline::Toggle)), +// theme.tooltip.clone(), +// cx, +// ) +// .aligned() +// .left() +// .into_any() +// } +// } + +impl ToolbarItemView for Breadcrumbs { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> ToolbarItemLocation { + cx.notify(); + self.active_item = None; + if let Some(item) = active_pane_item { + let this = cx.view().downgrade(); + self.subscription = Some(item.subscribe_to_item_events( + cx, + Box::new(move |event, cx| { + if let ItemEvent::UpdateBreadcrumbs = event { + this.update(cx, |_, cx| { + cx.emit(Event::UpdateLocation); + cx.notify(); + }) + .ok(); + } + }), + )); + self.active_item = Some(item.boxed_clone()); + item.breadcrumb_location(cx) + } else { + ToolbarItemLocation::Hidden + } + } + + // fn location_for_event( + // &self, + // _: &Event, + // current_location: ToolbarItemLocation, + // cx: &AppContext, + // ) -> ToolbarItemLocation { + // if let Some(active_item) = self.active_item.as_ref() { + // active_item.breadcrumb_location(cx) + // } else { + // current_location + // } + // } + + fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext) { + self.pane_focused = pane_focused; + } +} diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index a14088cc50..c1666e9c1d 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -346,7 +346,7 @@ impl Drop for PendingEntitySubscription { } } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub struct TelemetrySettings { pub diagnostics: bool, pub metrics: bool, diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index bbaf521e15..33c3c14ddd 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.29.0" +version = "0.29.1" publish = false [[bin]] diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 68b06e435d..780fb783bc 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1220,6 +1220,13 @@ impl Database { self.check_user_is_channel_admin(&new_parent, admin_id, &*tx) .await?; + if new_parent + .ancestors_including_self() + .any(|id| id == channel.id) + { + Err(anyhow!("cannot move a channel into one of its descendants"))?; + } + new_parent_path = new_parent.path(); new_parent_channel = Some(new_parent); } else { diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 43526c7f24..324917bbdd 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -450,6 +450,20 @@ async fn test_db_channel_moving_bugs(db: &Arc) { (livestreaming_id, &[projects_id]), ], ); + + // Can't move a channel into its ancestor + db.move_channel(projects_id, Some(livestreaming_id), user_id) + .await + .unwrap_err(); + let result = db.get_channels_for_user(user_id).await.unwrap(); + assert_channel_tree( + result.channels, + &[ + (zed_id, &[]), + (projects_id, &[]), + (livestreaming_id, &[projects_id]), + ], + ); } test_both_dbs!( diff --git a/crates/collab2/src/db/queries/channels.rs b/crates/collab2/src/db/queries/channels.rs index 68b06e435d..780fb783bc 100644 --- a/crates/collab2/src/db/queries/channels.rs +++ b/crates/collab2/src/db/queries/channels.rs @@ -1220,6 +1220,13 @@ impl Database { self.check_user_is_channel_admin(&new_parent, admin_id, &*tx) .await?; + if new_parent + .ancestors_including_self() + .any(|id| id == channel.id) + { + Err(anyhow!("cannot move a channel into one of its descendants"))?; + } + new_parent_path = new_parent.path(); new_parent_channel = Some(new_parent); } else { diff --git a/crates/collab2/src/db/tests/channel_tests.rs b/crates/collab2/src/db/tests/channel_tests.rs index 43526c7f24..8a7a19ed3a 100644 --- a/crates/collab2/src/db/tests/channel_tests.rs +++ b/crates/collab2/src/db/tests/channel_tests.rs @@ -420,8 +420,6 @@ async fn test_db_channel_moving_bugs(db: &Arc) { .await .unwrap(); - // Dag is: zed - projects - livestreaming - // Move to same parent should be a no-op assert!(db .move_channel(projects_id, Some(zed_id), user_id) @@ -450,6 +448,20 @@ async fn test_db_channel_moving_bugs(db: &Arc) { (livestreaming_id, &[projects_id]), ], ); + + // Can't move a channel into its ancestor + db.move_channel(projects_id, Some(livestreaming_id), user_id) + .await + .unwrap_err(); + let result = db.get_channels_for_user(user_id).await.unwrap(); + assert_channel_tree( + result.channels, + &[ + (zed_id, &[]), + (projects_id, &[]), + (livestreaming_id, &[projects_id]), + ], + ); } test_both_dbs!( diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index b90df68c2a..9b2af2cfb1 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -1,5 +1,5 @@ #![allow(unused)] -// mod channel_modal; +mod channel_modal; mod contact_finder; // use crate::{ @@ -192,6 +192,8 @@ use workspace::{ use crate::{face_pile::FacePile, CollaborationPanelSettings}; +use self::channel_modal::ChannelModal; + pub fn init(cx: &mut AppContext) { cx.observe_new_views(|workspace: &mut Workspace, _| { workspace.register_action(|workspace, _: &ToggleFocus, cx| { @@ -2058,13 +2060,11 @@ impl CollabPanel { } fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { - todo!(); - // self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx); + self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx); } fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { - todo!(); - // self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx); + self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx); } fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext) { @@ -2156,38 +2156,36 @@ impl CollabPanel { }) } - // fn show_channel_modal( - // &mut self, - // channel_id: ChannelId, - // mode: channel_modal::Mode, - // cx: &mut ViewContext, - // ) { - // let workspace = self.workspace.clone(); - // let user_store = self.user_store.clone(); - // let channel_store = self.channel_store.clone(); - // let members = self.channel_store.update(cx, |channel_store, cx| { - // channel_store.get_channel_member_details(channel_id, cx) - // }); + fn show_channel_modal( + &mut self, + channel_id: ChannelId, + mode: channel_modal::Mode, + cx: &mut ViewContext, + ) { + let workspace = self.workspace.clone(); + let user_store = self.user_store.clone(); + let channel_store = self.channel_store.clone(); + let members = self.channel_store.update(cx, |channel_store, cx| { + channel_store.get_channel_member_details(channel_id, cx) + }); - // cx.spawn(|_, mut cx| async move { - // let members = members.await?; - // workspace.update(&mut cx, |workspace, cx| { - // workspace.toggle_modal(cx, |_, cx| { - // cx.add_view(|cx| { - // ChannelModal::new( - // user_store.clone(), - // channel_store.clone(), - // channel_id, - // mode, - // members, - // cx, - // ) - // }) - // }); - // }) - // }) - // .detach(); - // } + cx.spawn(|_, mut cx| async move { + let members = members.await?; + workspace.update(&mut cx, |workspace, cx| { + workspace.toggle_modal(cx, |cx| { + ChannelModal::new( + user_store.clone(), + channel_store.clone(), + channel_id, + mode, + members, + cx, + ) + }); + }) + }) + .detach(); + } // fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext) { // self.remove_channel(action.channel_id, cx) diff --git a/crates/collab_ui2/src/collab_panel/channel_modal.rs b/crates/collab_ui2/src/collab_panel/channel_modal.rs index 0ccf0894b2..fc1a4c5fb7 100644 --- a/crates/collab_ui2/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui2/src/collab_panel/channel_modal.rs @@ -3,58 +3,54 @@ use client::{ proto::{self, ChannelRole, ChannelVisibility}, User, UserId, UserStore, }; -use context_menu::{ContextMenu, ContextMenuItem}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, - elements::*, - platform::{CursorStyle, MouseButton}, - AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext, - ViewHandle, + actions, div, AppContext, ClipboardItem, DismissEvent, Div, Entity, EventEmitter, + FocusableView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, + WeakView, }; -use picker::{Picker, PickerDelegate, PickerEvent}; +use picker::{Picker, PickerDelegate}; use std::sync::Arc; +use ui::v_stack; use util::TryFutureExt; -use workspace::Modal; actions!( - channel_modal, - [ - SelectNextControl, - ToggleMode, - ToggleMemberAdmin, - RemoveMember - ] + SelectNextControl, + ToggleMode, + ToggleMemberAdmin, + RemoveMember ); -pub fn init(cx: &mut AppContext) { - Picker::::init(cx); - cx.add_action(ChannelModal::toggle_mode); - cx.add_action(ChannelModal::toggle_member_admin); - cx.add_action(ChannelModal::remove_member); - cx.add_action(ChannelModal::dismiss); -} +// pub fn init(cx: &mut AppContext) { +// Picker::::init(cx); +// cx.add_action(ChannelModal::toggle_mode); +// cx.add_action(ChannelModal::toggle_member_admin); +// cx.add_action(ChannelModal::remove_member); +// cx.add_action(ChannelModal::dismiss); +// } pub struct ChannelModal { - picker: ViewHandle>, - channel_store: ModelHandle, + picker: View>, + channel_store: Model, channel_id: ChannelId, has_focus: bool, } impl ChannelModal { pub fn new( - user_store: ModelHandle, - channel_store: ModelHandle, + user_store: Model, + channel_store: Model, channel_id: ChannelId, mode: Mode, members: Vec, cx: &mut ViewContext, ) -> Self { cx.observe(&channel_store, |_, _, cx| cx.notify()).detach(); - let picker = cx.add_view(|cx| { + let channel_modal = cx.view().downgrade(); + let picker = cx.build_view(|cx| { Picker::new( ChannelModalDelegate { + channel_modal, matching_users: Vec::new(), matching_member_indices: Vec::new(), selected_index: 0, @@ -64,20 +60,17 @@ impl ChannelModal { match_candidates: Vec::new(), members, mode, - context_menu: cx.add_view(|cx| { - let mut menu = ContextMenu::new(cx.view_id(), cx); - menu.set_position_mode(OverlayPositionMode::Local); - menu - }), + // context_menu: cx.add_view(|cx| { + // let mut menu = ContextMenu::new(cx.view_id(), cx); + // menu.set_position_mode(OverlayPositionMode::Local); + // menu + // }), }, cx, ) - .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone()) }); - cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach(); - - let has_focus = picker.read(cx).has_focus(); + let has_focus = picker.focus_handle(cx).contains_focused(cx); Self { picker, @@ -88,7 +81,7 @@ impl ChannelModal { } fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext) { - let mode = match self.picker.read(cx).delegate().mode { + let mode = match self.picker.read(cx).delegate.mode { Mode::ManageMembers => Mode::InviteMembers, Mode::InviteMembers => Mode::ManageMembers, }; @@ -103,20 +96,20 @@ impl ChannelModal { let mut members = channel_store .update(&mut cx, |channel_store, cx| { channel_store.get_channel_member_details(channel_id, cx) - }) + })? .await?; members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key())); this.update(&mut cx, |this, cx| { this.picker - .update(cx, |picker, _| picker.delegate_mut().members = members); + .update(cx, |picker, _| picker.delegate.members = members); })?; } this.update(&mut cx, |this, cx| { this.picker.update(cx, |picker, cx| { - let delegate = picker.delegate_mut(); + let delegate = &mut picker.delegate; delegate.mode = mode; delegate.selected_index = 0; picker.set_query("", cx); @@ -131,203 +124,194 @@ impl ChannelModal { fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext) { self.picker.update(cx, |picker, cx| { - picker.delegate_mut().toggle_selected_member_admin(cx); + picker.delegate.toggle_selected_member_admin(cx); }) } fn remove_member(&mut self, _: &RemoveMember, cx: &mut ViewContext) { self.picker.update(cx, |picker, cx| { - picker.delegate_mut().remove_selected_member(cx); + picker.delegate.remove_selected_member(cx); }); } fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(PickerEvent::Dismiss); + cx.emit(DismissEvent); } } -impl Entity for ChannelModal { - type Event = PickerEvent; -} +impl EventEmitter for ChannelModal {} -impl View for ChannelModal { - fn ui_name() -> &'static str { - "ChannelModal" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &theme::current(cx).collab_panel.tabbed_modal; - - let mode = self.picker.read(cx).delegate().mode; - let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else { - return Empty::new().into_any(); - }; - - enum InviteMembers {} - enum ManageMembers {} - - fn render_mode_button( - mode: Mode, - text: &'static str, - current_mode: Mode, - theme: &theme::TabbedModal, - cx: &mut ViewContext, - ) -> AnyElement { - let active = mode == current_mode; - MouseEventHandler::new::(0, cx, move |state, _| { - let contained_text = theme.tab_button.style_for(active, state); - Label::new(text, contained_text.text.clone()) - .contained() - .with_style(contained_text.container.clone()) - }) - .on_click(MouseButton::Left, move |_, this, cx| { - if !active { - this.set_mode(mode, cx); - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .into_any() - } - - fn render_visibility( - channel_id: ChannelId, - visibility: ChannelVisibility, - theme: &theme::TabbedModal, - cx: &mut ViewContext, - ) -> AnyElement { - enum TogglePublic {} - - if visibility == ChannelVisibility::Members { - return Flex::row() - .with_child( - MouseEventHandler::new::(0, cx, move |state, _| { - let style = theme.visibility_toggle.style_for(state); - Label::new(format!("{}", "Public access: OFF"), style.text.clone()) - .contained() - .with_style(style.container.clone()) - }) - .on_click(MouseButton::Left, move |_, this, cx| { - this.channel_store - .update(cx, |channel_store, cx| { - channel_store.set_channel_visibility( - channel_id, - ChannelVisibility::Public, - cx, - ) - }) - .detach_and_log_err(cx); - }) - .with_cursor_style(CursorStyle::PointingHand), - ) - .into_any(); - } - - Flex::row() - .with_child( - MouseEventHandler::new::(0, cx, move |state, _| { - let style = theme.visibility_toggle.style_for(state); - Label::new(format!("{}", "Public access: ON"), style.text.clone()) - .contained() - .with_style(style.container.clone()) - }) - .on_click(MouseButton::Left, move |_, this, cx| { - this.channel_store - .update(cx, |channel_store, cx| { - channel_store.set_channel_visibility( - channel_id, - ChannelVisibility::Members, - cx, - ) - }) - .detach_and_log_err(cx); - }) - .with_cursor_style(CursorStyle::PointingHand), - ) - .with_spacing(14.0) - .with_child( - MouseEventHandler::new::(1, cx, move |state, _| { - let style = theme.channel_link.style_for(state); - Label::new(format!("{}", "copy link"), style.text.clone()) - .contained() - .with_style(style.container.clone()) - }) - .on_click(MouseButton::Left, move |_, this, cx| { - if let Some(channel) = - this.channel_store.read(cx).channel_for_id(channel_id) - { - let item = ClipboardItem::new(channel.link()); - cx.write_to_clipboard(item); - } - }) - .with_cursor_style(CursorStyle::PointingHand), - ) - .into_any() - } - - Flex::column() - .with_child( - Flex::column() - .with_child( - Label::new(format!("#{}", channel.name), theme.title.text.clone()) - .contained() - .with_style(theme.title.container.clone()), - ) - .with_child(render_visibility(channel.id, channel.visibility, theme, cx)) - .with_child(Flex::row().with_children([ - render_mode_button::( - Mode::InviteMembers, - "Invite members", - mode, - theme, - cx, - ), - render_mode_button::( - Mode::ManageMembers, - "Manage members", - mode, - theme, - cx, - ), - ])) - .expanded() - .contained() - .with_style(theme.header), - ) - .with_child( - ChildView::new(&self.picker, cx) - .contained() - .with_style(theme.body), - ) - .constrained() - .with_max_height(theme.max_height) - .with_max_width(theme.max_width) - .contained() - .with_style(theme.modal) - .into_any() - } - - fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; - if cx.is_self_focused() { - cx.focus(&self.picker) - } - } - - fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { - self.has_focus = false; +impl FocusableView for ChannelModal { + fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { + self.picker.focus_handle(cx) } } -impl Modal for ChannelModal { - fn has_focus(&self) -> bool { - self.has_focus +impl Render for ChannelModal { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + v_stack().min_w_96().child(self.picker.clone()) + // let theme = &theme::current(cx).collab_panel.tabbed_modal; + + // let mode = self.picker.read(cx).delegate().mode; + // let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else { + // return Empty::new().into_any(); + // }; + + // enum InviteMembers {} + // enum ManageMembers {} + + // fn render_mode_button( + // mode: Mode, + // text: &'static str, + // current_mode: Mode, + // theme: &theme::TabbedModal, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let active = mode == current_mode; + // MouseEventHandler::new::(0, cx, move |state, _| { + // let contained_text = theme.tab_button.style_for(active, state); + // Label::new(text, contained_text.text.clone()) + // .contained() + // .with_style(contained_text.container.clone()) + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // if !active { + // this.set_mode(mode, cx); + // } + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .into_any() + // } + + // fn render_visibility( + // channel_id: ChannelId, + // visibility: ChannelVisibility, + // theme: &theme::TabbedModal, + // cx: &mut ViewContext, + // ) -> AnyElement { + // enum TogglePublic {} + + // if visibility == ChannelVisibility::Members { + // return Flex::row() + // .with_child( + // MouseEventHandler::new::(0, cx, move |state, _| { + // let style = theme.visibility_toggle.style_for(state); + // Label::new(format!("{}", "Public access: OFF"), style.text.clone()) + // .contained() + // .with_style(style.container.clone()) + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.channel_store + // .update(cx, |channel_store, cx| { + // channel_store.set_channel_visibility( + // channel_id, + // ChannelVisibility::Public, + // cx, + // ) + // }) + // .detach_and_log_err(cx); + // }) + // .with_cursor_style(CursorStyle::PointingHand), + // ) + // .into_any(); + // } + + // Flex::row() + // .with_child( + // MouseEventHandler::new::(0, cx, move |state, _| { + // let style = theme.visibility_toggle.style_for(state); + // Label::new(format!("{}", "Public access: ON"), style.text.clone()) + // .contained() + // .with_style(style.container.clone()) + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.channel_store + // .update(cx, |channel_store, cx| { + // channel_store.set_channel_visibility( + // channel_id, + // ChannelVisibility::Members, + // cx, + // ) + // }) + // .detach_and_log_err(cx); + // }) + // .with_cursor_style(CursorStyle::PointingHand), + // ) + // .with_spacing(14.0) + // .with_child( + // MouseEventHandler::new::(1, cx, move |state, _| { + // let style = theme.channel_link.style_for(state); + // Label::new(format!("{}", "copy link"), style.text.clone()) + // .contained() + // .with_style(style.container.clone()) + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // if let Some(channel) = + // this.channel_store.read(cx).channel_for_id(channel_id) + // { + // let item = ClipboardItem::new(channel.link()); + // cx.write_to_clipboard(item); + // } + // }) + // .with_cursor_style(CursorStyle::PointingHand), + // ) + // .into_any() + // } + + // Flex::column() + // .with_child( + // Flex::column() + // .with_child( + // Label::new(format!("#{}", channel.name), theme.title.text.clone()) + // .contained() + // .with_style(theme.title.container.clone()), + // ) + // .with_child(render_visibility(channel.id, channel.visibility, theme, cx)) + // .with_child(Flex::row().with_children([ + // render_mode_button::( + // Mode::InviteMembers, + // "Invite members", + // mode, + // theme, + // cx, + // ), + // render_mode_button::( + // Mode::ManageMembers, + // "Manage members", + // mode, + // theme, + // cx, + // ), + // ])) + // .expanded() + // .contained() + // .with_style(theme.header), + // ) + // .with_child( + // ChildView::new(&self.picker, cx) + // .contained() + // .with_style(theme.body), + // ) + // .constrained() + // .with_max_height(theme.max_height) + // .with_max_width(theme.max_width) + // .contained() + // .with_style(theme.modal) + // .into_any() } - fn dismiss_on_event(event: &Self::Event) -> bool { - match event { - PickerEvent::Dismiss => true, - } - } + // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + // self.has_focus = true; + // if cx.is_self_focused() { + // cx.focus(&self.picker) + // } + // } + + // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + // self.has_focus = false; + // } } #[derive(Copy, Clone, PartialEq)] @@ -337,19 +321,22 @@ pub enum Mode { } pub struct ChannelModalDelegate { + channel_modal: WeakView, matching_users: Vec>, matching_member_indices: Vec, - user_store: ModelHandle, - channel_store: ModelHandle, + user_store: Model, + channel_store: Model, channel_id: ChannelId, selected_index: usize, mode: Mode, match_candidates: Vec, members: Vec, - context_menu: ViewHandle, + // context_menu: ViewHandle, } impl PickerDelegate for ChannelModalDelegate { + type ListItem = Div; + fn placeholder_text(&self) -> Arc { "Search collaborator by username...".into() } @@ -382,19 +369,19 @@ impl PickerDelegate for ChannelModalDelegate { } })); - let matches = cx.background().block(match_strings( + let matches = cx.background_executor().block(match_strings( &self.match_candidates, &query, true, usize::MAX, &Default::default(), - cx.background().clone(), + cx.background_executor().clone(), )); cx.spawn(|picker, mut cx| async move { picker .update(&mut cx, |picker, cx| { - let delegate = picker.delegate_mut(); + let delegate = &mut picker.delegate; delegate.matching_member_indices.clear(); delegate .matching_member_indices @@ -412,8 +399,7 @@ impl PickerDelegate for ChannelModalDelegate { async { let users = search_users.await?; picker.update(&mut cx, |picker, cx| { - let delegate = picker.delegate_mut(); - delegate.matching_users = users; + picker.delegate.matching_users = users; cx.notify(); })?; anyhow::Ok(()) @@ -445,138 +431,142 @@ impl PickerDelegate for ChannelModalDelegate { } fn dismissed(&mut self, cx: &mut ViewContext>) { - cx.emit(PickerEvent::Dismiss); + self.channel_modal + .update(cx, |_, cx| { + cx.emit(DismissEvent); + }) + .ok(); } fn render_match( &self, ix: usize, - mouse_state: &mut MouseState, selected: bool, - cx: &gpui::AppContext, - ) -> AnyElement> { - let full_theme = &theme::current(cx); - let theme = &full_theme.collab_panel.channel_modal; - let tabbed_modal = &full_theme.collab_panel.tabbed_modal; - let (user, role) = self.user_at_index(ix).unwrap(); - let request_status = self.member_status(user.id, cx); + cx: &mut ViewContext>, + ) -> Option { + None + // let full_theme = &theme::current(cx); + // let theme = &full_theme.collab_panel.channel_modal; + // let tabbed_modal = &full_theme.collab_panel.tabbed_modal; + // let (user, role) = self.user_at_index(ix).unwrap(); + // let request_status = self.member_status(user.id, cx); - let style = tabbed_modal - .picker - .item - .in_state(selected) - .style_for(mouse_state); + // let style = tabbed_modal + // .picker + // .item + // .in_state(selected) + // .style_for(mouse_state); - let in_manage = matches!(self.mode, Mode::ManageMembers); + // let in_manage = matches!(self.mode, Mode::ManageMembers); - let mut result = Flex::row() - .with_children(user.avatar.clone().map(|avatar| { - Image::from_data(avatar) - .with_style(theme.contact_avatar) - .aligned() - .left() - })) - .with_child( - Label::new(user.github_login.clone(), style.label.clone()) - .contained() - .with_style(theme.contact_username) - .aligned() - .left(), - ) - .with_children({ - (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then( - || { - Label::new("Invited", theme.member_tag.text.clone()) - .contained() - .with_style(theme.member_tag.container) - .aligned() - .left() - }, - ) - }) - .with_children(if in_manage && role == Some(ChannelRole::Admin) { - Some( - Label::new("Admin", theme.member_tag.text.clone()) - .contained() - .with_style(theme.member_tag.container) - .aligned() - .left(), - ) - } else if in_manage && role == Some(ChannelRole::Guest) { - Some( - Label::new("Guest", theme.member_tag.text.clone()) - .contained() - .with_style(theme.member_tag.container) - .aligned() - .left(), - ) - } else { - None - }) - .with_children({ - let svg = match self.mode { - Mode::ManageMembers => Some( - Svg::new("icons/ellipsis.svg") - .with_color(theme.member_icon.color) - .constrained() - .with_width(theme.member_icon.icon_width) - .aligned() - .constrained() - .with_width(theme.member_icon.button_width) - .with_height(theme.member_icon.button_width) - .contained() - .with_style(theme.member_icon.container), - ), - Mode::InviteMembers => match request_status { - Some(proto::channel_member::Kind::Member) => Some( - Svg::new("icons/check.svg") - .with_color(theme.member_icon.color) - .constrained() - .with_width(theme.member_icon.icon_width) - .aligned() - .constrained() - .with_width(theme.member_icon.button_width) - .with_height(theme.member_icon.button_width) - .contained() - .with_style(theme.member_icon.container), - ), - Some(proto::channel_member::Kind::Invitee) => Some( - Svg::new("icons/check.svg") - .with_color(theme.invitee_icon.color) - .constrained() - .with_width(theme.invitee_icon.icon_width) - .aligned() - .constrained() - .with_width(theme.invitee_icon.button_width) - .with_height(theme.invitee_icon.button_width) - .contained() - .with_style(theme.invitee_icon.container), - ), - Some(proto::channel_member::Kind::AncestorMember) | None => None, - }, - }; + // let mut result = Flex::row() + // .with_children(user.avatar.clone().map(|avatar| { + // Image::from_data(avatar) + // .with_style(theme.contact_avatar) + // .aligned() + // .left() + // })) + // .with_child( + // Label::new(user.github_login.clone(), style.label.clone()) + // .contained() + // .with_style(theme.contact_username) + // .aligned() + // .left(), + // ) + // .with_children({ + // (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then( + // || { + // Label::new("Invited", theme.member_tag.text.clone()) + // .contained() + // .with_style(theme.member_tag.container) + // .aligned() + // .left() + // }, + // ) + // }) + // .with_children(if in_manage && role == Some(ChannelRole::Admin) { + // Some( + // Label::new("Admin", theme.member_tag.text.clone()) + // .contained() + // .with_style(theme.member_tag.container) + // .aligned() + // .left(), + // ) + // } else if in_manage && role == Some(ChannelRole::Guest) { + // Some( + // Label::new("Guest", theme.member_tag.text.clone()) + // .contained() + // .with_style(theme.member_tag.container) + // .aligned() + // .left(), + // ) + // } else { + // None + // }) + // .with_children({ + // let svg = match self.mode { + // Mode::ManageMembers => Some( + // Svg::new("icons/ellipsis.svg") + // .with_color(theme.member_icon.color) + // .constrained() + // .with_width(theme.member_icon.icon_width) + // .aligned() + // .constrained() + // .with_width(theme.member_icon.button_width) + // .with_height(theme.member_icon.button_width) + // .contained() + // .with_style(theme.member_icon.container), + // ), + // Mode::InviteMembers => match request_status { + // Some(proto::channel_member::Kind::Member) => Some( + // Svg::new("icons/check.svg") + // .with_color(theme.member_icon.color) + // .constrained() + // .with_width(theme.member_icon.icon_width) + // .aligned() + // .constrained() + // .with_width(theme.member_icon.button_width) + // .with_height(theme.member_icon.button_width) + // .contained() + // .with_style(theme.member_icon.container), + // ), + // Some(proto::channel_member::Kind::Invitee) => Some( + // Svg::new("icons/check.svg") + // .with_color(theme.invitee_icon.color) + // .constrained() + // .with_width(theme.invitee_icon.icon_width) + // .aligned() + // .constrained() + // .with_width(theme.invitee_icon.button_width) + // .with_height(theme.invitee_icon.button_width) + // .contained() + // .with_style(theme.invitee_icon.container), + // ), + // Some(proto::channel_member::Kind::AncestorMember) | None => None, + // }, + // }; - svg.map(|svg| svg.aligned().flex_float().into_any()) - }) - .contained() - .with_style(style.container) - .constrained() - .with_height(tabbed_modal.row_height) - .into_any(); + // svg.map(|svg| svg.aligned().flex_float().into_any()) + // }) + // .contained() + // .with_style(style.container) + // .constrained() + // .with_height(tabbed_modal.row_height) + // .into_any(); - if selected { - result = Stack::new() - .with_child(result) - .with_child( - ChildView::new(&self.context_menu, cx) - .aligned() - .top() - .right(), - ) - .into_any(); - } + // if selected { + // result = Stack::new() + // .with_child(result) + // .with_child( + // ChildView::new(&self.context_menu, cx) + // .aligned() + // .top() + // .right(), + // ) + // .into_any(); + // } - result + // result } } @@ -623,7 +613,7 @@ impl ChannelModalDelegate { cx.spawn(|picker, mut cx| async move { update.await?; picker.update(&mut cx, |picker, cx| { - let this = picker.delegate_mut(); + let this = &mut picker.delegate; if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) { member.role = new_role; } @@ -644,7 +634,7 @@ impl ChannelModalDelegate { cx.spawn(|picker, mut cx| async move { update.await?; picker.update(&mut cx, |picker, cx| { - let this = picker.delegate_mut(); + let this = &mut picker.delegate; if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) { this.members.remove(ix); this.matching_member_indices.retain_mut(|member_ix| { @@ -683,7 +673,7 @@ impl ChannelModalDelegate { kind: proto::channel_member::Kind::Invitee, role: ChannelRole::Member, }; - let members = &mut this.delegate_mut().members; + let members = &mut this.delegate.members; match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) { Ok(ix) | Err(ix) => members.insert(ix, new_member), } @@ -695,23 +685,23 @@ impl ChannelModalDelegate { } fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext>) { - self.context_menu.update(cx, |context_menu, cx| { - context_menu.show( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("Remove", RemoveMember), - ContextMenuItem::action( - if role == ChannelRole::Admin { - "Make non-admin" - } else { - "Make admin" - }, - ToggleMemberAdmin, - ), - ], - cx, - ) - }) + // self.context_menu.update(cx, |context_menu, cx| { + // context_menu.show( + // Default::default(), + // AnchorCorner::TopRight, + // vec![ + // ContextMenuItem::action("Remove", RemoveMember), + // ContextMenuItem::action( + // if role == ChannelRole::Admin { + // "Make non-admin" + // } else { + // "Make admin" + // }, + // ToggleMemberAdmin, + // ), + // ], + // cx, + // ) + // }) } } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index f18e4cb2db..2cdf32ca36 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -35,16 +35,19 @@ use gpui::{ ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView, WindowBounds, }; -use project::Project; +use project::{Project, RepositoryEntry}; use theme::ActiveTheme; -use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle, IconButton, KeyBinding, Tooltip}; +use ui::{ + h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, + IconButton, IconElement, KeyBinding, Tooltip, +}; use util::ResultExt; use workspace::{notifications::NotifyResultExt, Workspace}; use crate::face_pile::FacePile; -// const MAX_PROJECT_NAME_LENGTH: usize = 40; -// const MAX_BRANCH_NAME_LENGTH: usize = 40; +const MAX_PROJECT_NAME_LENGTH: usize = 40; +const MAX_BRANCH_NAME_LENGTH: usize = 40; // actions!( // collab, @@ -100,17 +103,18 @@ impl Render for CollabTitlebarItem { .update(cx, |this, cx| this.call_state().remote_participants(cx)) .log_err() .flatten(); - let mic_icon = if self + let is_muted = self .workspace .update(cx, |this, cx| this.call_state().is_muted(cx)) .log_err() .flatten() - .unwrap_or_default() - { - ui::Icon::MicMute - } else { - ui::Icon::Mic - }; + .unwrap_or_default(); + let is_deafened = self + .workspace + .update(cx, |this, cx| this.call_state().is_deafened(cx)) + .log_err() + .flatten() + .unwrap_or_default(); let speakers_icon = if self .workspace .update(cx, |this, cx| this.call_state().is_deafened(cx)) @@ -146,56 +150,11 @@ impl Render for CollabTitlebarItem { .child( h_stack() .gap_1() - // TODO - Add player menu - .child( - div() - .border() - .border_color(gpui::red()) - .id("project_owner_indicator") - .child( - Button::new("player", "player") - .style(ButtonStyle::Subtle) - .color(Some(Color::Player(0))), - ) - .tooltip(move |cx| Tooltip::text("Toggle following", cx)), - ) - // TODO - Add project menu - .child( - div() - .border() - .border_color(gpui::red()) - .id("titlebar_project_menu_button") - .child( - Button::new("project_name", "project_name") - .style(ButtonStyle::Subtle), - ) - .tooltip(move |cx| Tooltip::text("Recent Projects", cx)), - ) - // TODO - Add git menu - .child( - div() - .border() - .border_color(gpui::red()) - .id("titlebar_git_menu_button") - .child( - Button::new("branch_name", "branch_name") - .style(ButtonStyle::Subtle) - .color(Some(Color::Muted)), - ) - .tooltip(move |cx| { - cx.build_view(|_| { - Tooltip::new("Recent Branches") - .key_binding(KeyBinding::new(gpui::KeyBinding::new( - "cmd-b", - // todo!() Replace with real action. - gpui::NoAction, - None, - ))) - .meta("Only local branches shown") - }) - .into() - }), - ), + .when(is_in_room, |this| { + this.children(self.render_project_owner(cx)) + }) + .child(self.render_project_name(cx)) + .children(self.render_project_branch(cx)), ) .when_some( users.zip(current_user.clone()), @@ -236,62 +195,126 @@ impl Render for CollabTitlebarItem { .when(is_in_room, |this| { this.child( h_stack() + .gap_1() .child( h_stack() - .child(Button::new( - "toggle_sharing", - if is_shared { "Unshare" } else { "Share" }, - )) - .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({ - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().hang_up(cx).detach(); - }) - .log_err(); - } - })), + .gap_1() + .child( + Button::new( + "toggle_sharing", + if is_shared { "Unshare" } else { "Share" }, + ) + .style(ButtonStyle::Subtle), + ) + .child( + IconButton::new("leave-call", ui::Icon::Exit) + .style(ButtonStyle::Subtle) + .on_click({ + let workspace = workspace.clone(); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state().hang_up(cx).detach(); + }) + .log_err(); + } + }), + ), ) .child( h_stack() - .child(IconButton::new("mute-microphone", mic_icon).on_click({ - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().toggle_mute(cx); - }) - .log_err(); - } - })) - .child(IconButton::new("mute-sound", speakers_icon).on_click({ - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().toggle_deafen(cx); - }) - .log_err(); - } - })) - .child(IconButton::new("screen-share", ui::Icon::Screen).on_click( - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().toggle_screen_share(cx); - }) - .log_err(); - }, - )) + .gap_1() + .child( + IconButton::new( + "mute-microphone", + if is_muted { + ui::Icon::MicMute + } else { + ui::Icon::Mic + }, + ) + .style(ButtonStyle::Subtle) + .selected(is_muted) + .on_click({ + let workspace = workspace.clone(); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state().toggle_mute(cx); + }) + .log_err(); + } + }), + ) + .child( + IconButton::new("mute-sound", speakers_icon) + .style(ButtonStyle::Subtle) + .selected(is_deafened.clone()) + .tooltip(move |cx| { + Tooltip::with_meta( + "Deafen Audio", + None, + "Mic will be muted", + cx, + ) + }) + .on_click({ + let workspace = workspace.clone(); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state().toggle_deafen(cx); + }) + .log_err(); + } + }), + ) + .child( + IconButton::new("screen-share", ui::Icon::Screen) + .style(ButtonStyle::Subtle) + .on_click(move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state().toggle_screen_share(cx); + }) + .log_err(); + }), + ) .pl_2(), ), ) }) - .map(|this| { + .child(h_stack().px_1p5().map(|this| { if let Some(user) = current_user { this.when_some(user.avatar.clone(), |this, avatar| { - this.child(ui::Avatar::data(avatar)) + // TODO: Finish implementing user menu popover + // + this.child( + popover_menu("user-menu") + .menu(|cx| ContextMenu::build(cx, |menu, _| menu.header("ADADA"))) + .trigger( + ButtonLike::new("user-menu") + .child( + h_stack().gap_0p5().child(Avatar::data(avatar)).child( + IconElement::new(Icon::ChevronDown) + .color(Color::Muted), + ), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + ) + .anchor(gpui::AnchorCorner::TopRight), + ) + // this.child( + // ButtonLike::new("user-menu") + // .child( + // h_stack().gap_0p5().child(Avatar::data(avatar)).child( + // IconElement::new(Icon::ChevronDown).color(Color::Muted), + // ), + // ) + // .style(ButtonStyle::Subtle) + // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + // ) }) } else { this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| { @@ -305,7 +328,7 @@ impl Render for CollabTitlebarItem { .detach(); })) } - }) + })) } } @@ -424,6 +447,110 @@ impl CollabTitlebarItem { } } + // resolve if you are in a room -> render_project_owner + // render_project_owner -> resolve if you are in a room -> Option + + pub fn render_project_owner(&self, cx: &mut ViewContext) -> Option { + // TODO: We can't finish implementing this until project sharing works + // - [ ] Show the project owner when the project is remote (maybe done) + // - [x] Show the project owner when the project is local + // - [ ] Show the project owner with a lock icon when the project is local and unshared + + let remote_id = self.project.read(cx).remote_id(); + let is_local = remote_id.is_none(); + let is_shared = self.project.read(cx).is_shared(); + let (user_name, participant_index) = { + if let Some(host) = self.project.read(cx).host() { + debug_assert!(!is_local); + let (Some(host_user), Some(participant_index)) = ( + self.user_store.read(cx).get_cached_user(host.user_id), + self.user_store + .read(cx) + .participant_indices() + .get(&host.user_id), + ) else { + return None; + }; + (host_user.github_login.clone(), participant_index.0) + } else { + debug_assert!(is_local); + let name = self + .user_store + .read(cx) + .current_user() + .map(|user| user.github_login.clone())?; + (name, 0) + } + }; + Some( + div().border().border_color(gpui::red()).child( + Button::new( + "project_owner_trigger", + format!("{user_name} ({})", !is_shared), + ) + .color(Color::Player(participant_index)) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Toggle following", cx)), + ), + ) + } + + pub fn render_project_name(&self, cx: &mut ViewContext) -> impl Element { + let name = { + let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| { + let worktree = worktree.read(cx); + worktree.root_name() + }); + + names.next().unwrap_or("") + }; + + let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH); + + div().border().border_color(gpui::red()).child( + Button::new("project_name_trigger", name) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Recent Projects", cx)), + ) + } + + pub fn render_project_branch(&self, cx: &mut ViewContext) -> Option { + let entry = { + let mut names_and_branches = + self.project.read(cx).visible_worktrees(cx).map(|worktree| { + let worktree = worktree.read(cx); + worktree.root_git_entry() + }); + + names_and_branches.next().flatten() + }; + + let branch_name = entry + .as_ref() + .and_then(RepositoryEntry::branch) + .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?; + + Some( + div().border().border_color(gpui::red()).child( + Button::new("project_branch_trigger", branch_name) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| { + cx.build_view(|_| { + Tooltip::new("Recent Branches") + .key_binding(KeyBinding::new(gpui::KeyBinding::new( + "cmd-b", + // todo!() Replace with real action. + gpui::NoAction, + None, + ))) + .meta("Local branches only") + }) + .into() + }), + ), + ) + } + // fn collect_title_root_names( // &self, // theme: Arc, diff --git a/crates/copilot_button2/Cargo.toml b/crates/copilot_button2/Cargo.toml new file mode 100644 index 0000000000..9793ecfb15 --- /dev/null +++ b/crates/copilot_button2/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "copilot_button2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/copilot_button.rs" +doctest = false + +[dependencies] +copilot = { package = "copilot2", path = "../copilot2" } +editor = { package = "editor2", path = "../editor2" } +fs = { package = "fs2", path = "../fs2" } +zed-actions = { package="zed_actions2", path = "../zed_actions2"} +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +settings = { package = "settings2", path = "../settings2" } +theme = { package = "theme2", path = "../theme2" } +util = { path = "../util" } +workspace = { package = "workspace2", path = "../workspace2" } +anyhow.workspace = true +smol.workspace = true +futures.workspace = true + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } diff --git a/crates/copilot_button2/src/copilot_button.rs b/crates/copilot_button2/src/copilot_button.rs new file mode 100644 index 0000000000..aab59a9cad --- /dev/null +++ b/crates/copilot_button2/src/copilot_button.rs @@ -0,0 +1,371 @@ +#![allow(unused)] +use anyhow::Result; +use copilot::{Copilot, SignOut, Status}; +use editor::{scroll::autoscroll::Autoscroll, Editor}; +use fs::Fs; +use gpui::{ + div, Action, AnchorCorner, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity, + ParentElement, Render, Subscription, View, ViewContext, WeakView, WindowContext, +}; +use language::{ + language_settings::{self, all_language_settings, AllLanguageSettings}, + File, Language, +}; +use settings::{update_settings_file, Settings, SettingsStore}; +use std::{path::Path, sync::Arc}; +use util::{paths, ResultExt}; +use workspace::{ + create_and_open_local_file, + item::ItemHandle, + ui::{ + popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, PopoverMenu, Tooltip, + }, + StatusItemView, Toast, Workspace, +}; +use zed_actions::OpenBrowser; + +const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot"; +const COPILOT_STARTING_TOAST_ID: usize = 1337; +const COPILOT_ERROR_TOAST_ID: usize = 1338; + +pub struct CopilotButton { + editor_subscription: Option<(Subscription, usize)>, + editor_enabled: Option, + language: Option>, + file: Option>, + fs: Arc, +} + +impl Render for CopilotButton { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let all_language_settings = all_language_settings(None, cx); + if !all_language_settings.copilot.feature_enabled { + return div(); + } + + let Some(copilot) = Copilot::global(cx) else { + return div(); + }; + let status = copilot.read(cx).status(); + + let enabled = self + .editor_enabled + .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); + + let icon = match status { + Status::Error(_) => Icon::CopilotError, + Status::Authorized => { + if enabled { + Icon::Copilot + } else { + Icon::CopilotDisabled + } + } + _ => Icon::CopilotInit, + }; + + if let Status::Error(e) = status { + return div().child( + IconButton::new("copilot-error", icon) + .on_click(cx.listener(move |this, _, cx| { + if let Some(workspace) = cx.window_handle().downcast::() { + workspace.update(cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + COPILOT_ERROR_TOAST_ID, + format!("Copilot can't be started: {}", e), + ) + .on_click( + "Reinstall Copilot", + |cx| { + if let Some(copilot) = Copilot::global(cx) { + copilot + .update(cx, |copilot, cx| copilot.reinstall(cx)) + .detach(); + } + }, + ), + cx, + ); + }); + } + })) + .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)), + ); + } + let this = cx.view().clone(); + + div().child( + popover_menu("copilot") + .menu(move |cx| match status { + Status::Authorized => this.update(cx, |this, cx| this.build_copilot_menu(cx)), + _ => this.update(cx, |this, cx| this.build_copilot_start_menu(cx)), + }) + .anchor(AnchorCorner::BottomRight) + .trigger( + IconButton::new("copilot-icon", icon) + .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)), + ), + ) + } +} + +impl CopilotButton { + pub fn new(fs: Arc, cx: &mut ViewContext) -> Self { + Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach()); + + cx.observe_global::(move |_, cx| cx.notify()) + .detach(); + + Self { + editor_subscription: None, + editor_enabled: None, + language: None, + file: None, + fs, + } + } + + pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext) -> View { + let fs = self.fs.clone(); + ContextMenu::build(cx, |menu, cx| { + menu.entry("Sign In", initiate_sign_in) + .entry("Disable Copilot", move |cx| hide_copilot(fs.clone(), cx)) + }) + } + + pub fn build_copilot_menu(&mut self, cx: &mut ViewContext) -> View { + let fs = self.fs.clone(); + + return ContextMenu::build(cx, move |mut menu, cx| { + if let Some(language) = self.language.clone() { + let fs = fs.clone(); + let language_enabled = + language_settings::language_settings(Some(&language), None, cx) + .show_copilot_suggestions; + + menu = menu.entry( + format!( + "{} Suggestions for {}", + if language_enabled { "Hide" } else { "Show" }, + language.name() + ), + move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx), + ); + } + + let settings = AllLanguageSettings::get_global(cx); + + if let Some(file) = &self.file { + let path = file.path().clone(); + let path_enabled = settings.copilot_enabled_for_path(&path); + + menu = menu.entry( + format!( + "{} Suggestions for This Path", + if path_enabled { "Hide" } else { "Show" } + ), + move |cx| { + if let Some(workspace) = cx.window_handle().downcast::() { + if let Ok(workspace) = workspace.root_view(cx) { + let workspace = workspace.downgrade(); + cx.spawn(|cx| { + configure_disabled_globs( + workspace, + path_enabled.then_some(path.clone()), + cx, + ) + }) + .detach_and_log_err(cx); + } + } + }, + ); + } + + let globally_enabled = settings.copilot_enabled(None, None); + menu.entry( + if globally_enabled { + "Hide Suggestions for All Files" + } else { + "Show Suggestions for All Files" + }, + move |cx| toggle_copilot_globally(fs.clone(), cx), + ) + .separator() + .link( + "Copilot Settings", + OpenBrowser { + url: COPILOT_SETTINGS_URL.to_string(), + } + .boxed_clone(), + cx, + ) + .action("Sign Out", SignOut.boxed_clone(), cx) + }); + } + + pub fn update_enabled(&mut self, editor: View, cx: &mut ViewContext) { + let editor = editor.read(cx); + let snapshot = editor.buffer().read(cx).snapshot(cx); + let suggestion_anchor = editor.selections.newest_anchor().start; + let language = snapshot.language_at(suggestion_anchor); + let file = snapshot.file_at(suggestion_anchor).cloned(); + + self.editor_enabled = Some( + all_language_settings(self.file.as_ref(), cx) + .copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())), + ); + self.language = language.cloned(); + self.file = file; + + cx.notify() + } +} + +impl StatusItemView for CopilotButton { + fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext) { + if let Some(editor) = item.map(|item| item.act_as::(cx)).flatten() { + self.editor_subscription = Some(( + cx.observe(&editor, Self::update_enabled), + editor.entity_id().as_u64() as usize, + )); + self.update_enabled(editor, cx); + } else { + self.language = None; + self.editor_subscription = None; + self.editor_enabled = None; + } + cx.notify(); + } +} + +async fn configure_disabled_globs( + workspace: WeakView, + path_to_disable: Option>, + mut cx: AsyncWindowContext, +) -> Result<()> { + let settings_editor = workspace + .update(&mut cx, |_, cx| { + create_and_open_local_file(&paths::SETTINGS, cx, || { + settings::initial_user_settings_content().as_ref().into() + }) + })? + .await? + .downcast::() + .unwrap(); + + settings_editor.downgrade().update(&mut cx, |item, cx| { + let text = item.buffer().read(cx).snapshot(cx).text(); + + let settings = cx.global::(); + let edits = settings.edits_for_update::(&text, |file| { + let copilot = file.copilot.get_or_insert_with(Default::default); + let globs = copilot.disabled_globs.get_or_insert_with(|| { + settings + .get::(None) + .copilot + .disabled_globs + .iter() + .map(|glob| glob.glob().to_string()) + .collect() + }); + + if let Some(path_to_disable) = &path_to_disable { + globs.push(path_to_disable.to_string_lossy().into_owned()); + } else { + globs.clear(); + } + }); + + if !edits.is_empty() { + item.change_selections(Some(Autoscroll::newest()), cx, |selections| { + selections.select_ranges(edits.iter().map(|e| e.0.clone())); + }); + + // When *enabling* a path, don't actually perform an edit, just select the range. + if path_to_disable.is_some() { + item.edit(edits.iter().cloned(), cx); + } + } + })?; + + anyhow::Ok(()) +} + +fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { + let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None); + update_settings_file::(fs, cx, move |file| { + file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) + }); +} + +fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { + let show_copilot_suggestions = + all_language_settings(None, cx).copilot_enabled(Some(&language), None); + update_settings_file::(fs, cx, move |file| { + file.languages + .entry(language.name()) + .or_default() + .show_copilot_suggestions = Some(!show_copilot_suggestions); + }); +} + +fn hide_copilot(fs: Arc, cx: &mut AppContext) { + update_settings_file::(fs, cx, move |file| { + file.features.get_or_insert(Default::default()).copilot = Some(false); + }); +} + +fn initiate_sign_in(cx: &mut WindowContext) { + let Some(copilot) = Copilot::global(cx) else { + return; + }; + let status = copilot.read(cx).status(); + + match status { + Status::Starting { task } => { + let Some(workspace) = cx.window_handle().downcast::() else { + return; + }; + + let Ok(workspace) = workspace.update(cx, |workspace, cx| { + workspace.show_toast( + Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."), + cx, + ); + workspace.weak_handle() + }) else { + return; + }; + + cx.spawn(|mut cx| async move { + task.await; + if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() { + workspace + .update(&mut cx, |workspace, cx| match copilot.read(cx).status() { + Status::Authorized => workspace.show_toast( + Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot has started!"), + cx, + ), + _ => { + workspace.dismiss_toast(COPILOT_STARTING_TOAST_ID, cx); + copilot + .update(cx, |copilot, cx| copilot.sign_in(cx)) + .detach_and_log_err(cx); + } + }) + .log_err(); + } + }) + .detach(); + } + _ => { + copilot + .update(cx, |copilot, cx| copilot.sign_in(cx)) + .detach_and_log_err(cx); + } + } +} diff --git a/crates/diagnostics2/src/diagnostics.rs b/crates/diagnostics2/src/diagnostics.rs index 0a0f4da893..dd01f90b9f 100644 --- a/crates/diagnostics2/src/diagnostics.rs +++ b/crates/diagnostics2/src/diagnostics.rs @@ -774,24 +774,39 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { Arc::new(move |_| { h_stack() .id("diagnostic header") - .gap_3() - .bg(gpui::red()) - .map(|stack| { - let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { - IconElement::new(Icon::XCircle).color(Color::Error) - } else { - IconElement::new(Icon::ExclamationTriangle).color(Color::Warning) - }; - - stack.child(div().pl_8().child(icon)) - }) - .when_some(diagnostic.source.as_ref(), |stack, source| { - stack.child(Label::new(format!("{source}:")).color(Color::Accent)) - }) - .child(HighlightedLabel::new(message.clone(), highlights.clone())) - .when_some(diagnostic.code.as_ref(), |stack, code| { - stack.child(Label::new(code.clone())) - }) + .py_2() + .pl_10() + .pr_5() + .w_full() + .justify_between() + .gap_2() + .child( + h_stack() + .gap_3() + .map(|stack| { + let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { + IconElement::new(Icon::XCircle).color(Color::Error) + } else { + IconElement::new(Icon::ExclamationTriangle).color(Color::Warning) + }; + stack.child(icon) + }) + .child( + h_stack() + .gap_1() + .child(HighlightedLabel::new(message.clone(), highlights.clone())) + .when_some(diagnostic.code.as_ref(), |stack, code| { + stack.child(Label::new(format!("({code})")).color(Color::Muted)) + }), + ), + ) + .child( + h_stack() + .gap_1() + .when_some(diagnostic.source.as_ref(), |stack, source| { + stack.child(Label::new(format!("{source}")).color(Color::Muted)) + }), + ) .into_any_element() }) } @@ -802,11 +817,22 @@ pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement { label.into_any_element() } else { h_stack() - .bg(gpui::red()) - .child(IconElement::new(Icon::XCircle)) - .child(Label::new(summary.error_count.to_string())) - .child(IconElement::new(Icon::ExclamationTriangle)) - .child(Label::new(summary.warning_count.to_string())) + .gap_1() + .when(summary.error_count > 0, |then| { + then.child( + h_stack() + .gap_1() + .child(IconElement::new(Icon::XCircle).color(Color::Error)) + .child(Label::new(summary.error_count.to_string())), + ) + }) + .when(summary.warning_count > 0, |then| { + then.child( + h_stack() + .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)) + .child(Label::new(summary.warning_count.to_string())), + ) + }) .into_any_element() } } diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index c180d49ef2..529438648a 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -100,8 +100,10 @@ use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; -use ui::prelude::*; -use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, Tooltip}; +use ui::{ + h_stack, v_stack, ButtonSize, ButtonStyle, HighlightedLabel, Icon, IconButton, Popover, Tooltip, +}; +use ui::{prelude::*, IconSize}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ item::{ItemEvent, ItemHandle}, @@ -154,7 +156,6 @@ pub fn render_parsed_markdown( } }), ); - let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights); let mut links = Vec::new(); let mut link_ranges = Vec::new(); @@ -167,7 +168,7 @@ pub fn render_parsed_markdown( InteractiveText::new( element_id, - StyledText::new(parsed.text.clone()).with_runs(runs), + StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights), ) .on_click(link_ranges, move |clicked_range_ix, cx| { match &links[clicked_range_ix] { @@ -1199,11 +1200,7 @@ impl CompletionsMenu { ), ); let completion_label = StyledText::new(completion.label.text.clone()) - .with_runs(text_runs_for_highlights( - &completion.label.text, - &style.text, - highlights, - )); + .with_highlights(&style.text, highlights); let documentation_label = if let Some(Documentation::SingleLine(text)) = documentation { Some(SharedString::from(text.clone())) @@ -1925,14 +1922,14 @@ impl Editor { // self.buffer.read(cx).read(cx).file_at(point).cloned() // } - // pub fn active_excerpt( - // &self, - // cx: &AppContext, - // ) -> Option<(ExcerptId, Model, Range)> { - // self.buffer - // .read(cx) - // .excerpt_containing(self.selections.newest_anchor().head(), cx) - // } + pub fn active_excerpt( + &self, + cx: &AppContext, + ) -> Option<(ExcerptId, Model, Range)> { + self.buffer + .read(cx) + .excerpt_containing(self.selections.newest_anchor().head(), cx) + } // pub fn style(&self, cx: &AppContext) -> EditorStyle { // build_style( @@ -9699,20 +9696,42 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend let message = diagnostic.message; Arc::new(move |cx: &mut BlockContext| { let message = message.clone(); + let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into(); + let write_to_clipboard = cx.write_to_clipboard(ClipboardItem::new(message.clone())); + + // TODO: Nate: We should tint the background of the block with the severity color + // We need to extend the theme before we can do this v_stack() .id(cx.block_id) + .relative() .size_full() .bg(gpui::red()) .children(highlighted_lines.iter().map(|(line, highlights)| { - div() + let group_id = cx.block_id.to_string(); + + h_stack() + .group(group_id.clone()) + .gap_2() + .absolute() + .left(cx.anchor_x) + .px_1p5() .child(HighlightedLabel::new(line.clone(), highlights.clone())) - .ml(cx.anchor_x) + .child( + div() + .border() + .border_color(gpui::red()) + .invisible() + .group_hover(group_id, |style| style.visible()) + .child( + IconButton::new(copy_id.clone(), Icon::Copy) + .icon_color(Color::Muted) + .size(ButtonSize::Compact) + .style(ButtonStyle::Transparent) + .on_click(cx.listener(move |_, _, cx| write_to_clipboard)) + .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)), + ), + ) })) - .cursor_pointer() - .on_click(cx.listener(move |_, _, cx| { - cx.write_to_clipboard(ClipboardItem::new(message.clone())); - })) - .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)) .into_any_element() }) } @@ -9760,31 +9779,6 @@ pub fn diagnostic_style( } } -pub fn text_runs_for_highlights( - text: &str, - default_style: &TextStyle, - highlights: impl IntoIterator, HighlightStyle)>, -) -> Vec { - let mut runs = Vec::new(); - let mut ix = 0; - for (range, highlight) in highlights { - if ix < range.start { - runs.push(default_style.clone().to_run(range.start - ix)); - } - runs.push( - default_style - .clone() - .highlight(highlight) - .to_run(range.len()), - ); - ix = range.end; - } - if ix < text.len() { - runs.push(default_style.to_run(text.len() - ix)); - } - runs -} - pub fn styled_runs_for_code_label<'a>( label: &'a CodeLabel, syntax_theme: &'a theme::SyntaxTheme, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 8f555ba9de..27d27a5947 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -51,8 +51,10 @@ use std::{ }; use sum_tree::Bias; use theme::{ActiveTheme, PlayerColor}; -use ui::prelude::*; -use ui::{h_stack, IconButton, Tooltip}; +use ui::{ + h_stack, ButtonLike, ButtonStyle, Disclosure, IconButton, IconElement, IconSize, Label, Tooltip, +}; +use ui::{prelude::*, Icon}; use util::ResultExt; use workspace::item::Item; @@ -2223,7 +2225,8 @@ impl EditorElement { .as_ref() .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) .unwrap_or_default(); - let jump_icon = project::File::from_dyn(buffer.file()).map(|file| { + + let jump_handler = project::File::from_dyn(buffer.file()).map(|file| { let jump_path = ProjectPath { worktree_id: file.worktree_id(cx), path: file.path.clone(), @@ -2234,11 +2237,11 @@ impl EditorElement { .map_or(range.context.start, |primary| primary.start); let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); - IconButton::new(block_id, ui::Icon::ArrowUpRight) - .on_click(cx.listener_for(&self.editor, move |editor, e, cx| { - editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); - })) - .tooltip(|cx| Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)) + let jump_handler = cx.listener_for(&self.editor, move |editor, e, cx| { + editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); + }); + + jump_handler }); let element = if *starts_new_buffer { @@ -2253,25 +2256,108 @@ impl EditorElement { .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/")); } - h_stack() - .id("path header block") - .size_full() - .bg(gpui::red()) - .child( - filename - .map(SharedString::from) - .unwrap_or_else(|| "untitled".into()), - ) - .children(parent_path) - .children(jump_icon) // .p_x(gutter_padding) + let is_open = true; + + div().id("path header container").size_full().p_1p5().child( + h_stack() + .id("path header block") + .py_1p5() + .pl_3() + .pr_2() + .rounded_lg() + .shadow_md() + .border() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().editor_subheader_background) + .justify_between() + .cursor_pointer() + .hover(|style| style.bg(cx.theme().colors().element_hover)) + .on_click(cx.listener(|_editor, _event, _cx| { + // TODO: Implement collapsing path headers + todo!("Clicking path header") + })) + .child( + h_stack() + .gap_3() + // TODO: Add open/close state and toggle action + .child( + div().border().border_color(gpui::red()).child( + ButtonLike::new("path-header-disclosure-control") + .style(ButtonStyle::Subtle) + .child(IconElement::new(match is_open { + true => Icon::ChevronDown, + false => Icon::ChevronRight, + })), + ), + ) + .child( + h_stack() + .gap_2() + .child(Label::new( + filename + .map(SharedString::from) + .unwrap_or_else(|| "untitled".into()), + )) + .when_some(parent_path, |then, path| { + then.child(Label::new(path).color(Color::Muted)) + }), + ), + ) + .children(jump_handler.map(|jump_handler| { + IconButton::new(block_id, Icon::ArrowUpRight) + .style(ButtonStyle::Subtle) + .on_click(jump_handler) + .tooltip(|cx| { + Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx) + }) + })), // .p_x(gutter_padding) + ) } else { let text_style = style.text.clone(); h_stack() .id("collapsed context") .size_full() - .bg(gpui::red()) - .child("⋯") - .children(jump_icon) // .p_x(gutter_padding) + .gap(gutter_padding) + .child( + h_stack() + .justify_end() + .flex_none() + .w(gutter_width - gutter_padding) + .h_full() + .text_buffer(cx) + .text_color(cx.theme().colors().editor_line_number) + .child("..."), + ) + .map(|this| { + if let Some(jump_handler) = jump_handler { + this.child( + ButtonLike::new("jump to collapsed context") + .style(ButtonStyle::Transparent) + .full_width() + .on_click(jump_handler) + .tooltip(|cx| { + Tooltip::for_action( + "Jump to Buffer", + &OpenExcerpts, + cx, + ) + }) + .child( + div() + .h_px() + .w_full() + .bg(cx.theme().colors().border_variant) + .group_hover("", |style| { + style.bg(cx.theme().colors().border) + }), + ), + ) + } else { + this.child(div().size_full().bg(gpui::green())) + } + }) + // .child("⋯") + // .children(jump_icon) // .p_x(gutter_padding) }; element.into_any() } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index ced0a4767c..68dca4c9d1 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -992,10 +992,6 @@ impl Interactivity { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; - } - let is_hovered = interactive_bounds.visibly_contains(&event.position, cx) && pending_mouse_down.borrow().is_none(); if !is_hovered { @@ -1003,6 +999,10 @@ impl Interactivity { return; } + if phase != DispatchPhase::Bubble { + return; + } + if active_tooltip.borrow().is_none() { let task = cx.spawn({ let active_tooltip = active_tooltip.clone(); diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 490d0b61a1..d398b1f8fe 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,6 +1,7 @@ use crate::{ - Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent, - Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine, + Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId, + MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle, + WhiteSpace, WindowContext, WrappedLine, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -87,7 +88,28 @@ impl StyledText { } } - pub fn with_runs(mut self, runs: Vec) -> Self { + pub fn with_highlights( + mut self, + default_style: &TextStyle, + highlights: impl IntoIterator, HighlightStyle)>, + ) -> Self { + let mut runs = Vec::new(); + let mut ix = 0; + for (range, highlight) in highlights { + if ix < range.start { + runs.push(default_style.clone().to_run(range.start - ix)); + } + runs.push( + default_style + .clone() + .highlight(highlight) + .to_run(range.len()), + ); + ix = range.end; + } + if ix < self.text.len() { + runs.push(default_style.to_run(self.text.len() - ix)); + } self.runs = Some(runs); self } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index ed45d7bab2..651392c9c8 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -472,13 +472,27 @@ pub enum PromptLevel { Critical, } +/// The style of the cursor (pointer) #[derive(Copy, Clone, Debug)] pub enum CursorStyle { Arrow, - ResizeLeftRight, - ResizeUpDown, - PointingHand, IBeam, + Crosshair, + ClosedHand, + OpenHand, + PointingHand, + ResizeLeft, + ResizeRight, + ResizeLeftRight, + ResizeUp, + ResizeDown, + ResizeUpDown, + DisappearingItem, + IBeamCursorForVerticalLayout, + OperationNotAllowed, + DragLink, + DragCopy, + ContextualMenu, } impl Default for CursorStyle { diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 7065c02e87..314f055811 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -724,16 +724,35 @@ impl Platform for MacPlatform { } } + /// Match cursor style to one of the styles available + /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor). fn set_cursor_style(&self, style: CursorStyle) { unsafe { let new_cursor: id = match style { CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], - CursorStyle::ResizeLeftRight => { - msg_send![class!(NSCursor), resizeLeftRightCursor] - } - CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], - CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], + CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor], + CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor], + CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor], + CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], + CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor], + CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor], + CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor], + CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor], + CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor], + CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], + CursorStyle::DisappearingItem => { + msg_send![class!(NSCursor), disappearingItemCursor] + } + CursorStyle::IBeamCursorForVerticalLayout => { + msg_send![class!(NSCursor), IBeamCursorForVerticalLayout] + } + CursorStyle::OperationNotAllowed => { + msg_send![class!(NSCursor), operationNotAllowedCursor] + } + CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor], + CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor], + CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor], }; let old_cursor: id = msg_send![class!(NSCursor), currentCursor]; diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 0b0c007b3b..fa4b6e18c5 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -238,11 +238,11 @@ impl Platform for TestPlatform { true } - fn write_to_clipboard(&self, item: crate::ClipboardItem) { + fn write_to_clipboard(&self, item: ClipboardItem) { *self.current_clipboard_item.lock() = Some(item); } - fn read_from_clipboard(&self) -> Option { + fn read_from_clipboard(&self) -> Option { self.current_clipboard_item.lock().clone() } diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index 77756154b5..346c1a760d 100644 --- a/crates/gpui2/src/styled.rs +++ b/crates/gpui2/src/styled.rs @@ -101,6 +101,125 @@ pub trait Styled: Sized { self } + /// Sets cursor style when hovering over an element to `text`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_text(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::IBeam); + self + } + + /// Sets cursor style when hovering over an element to `move`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_move(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ClosedHand); + self + } + + /// Sets cursor style when hovering over an element to `not-allowed`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_not_allowed(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed); + self + } + + /// Sets cursor style when hovering over an element to `context-menu`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_context_menu(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ContextualMenu); + self + } + + /// Sets cursor style when hovering over an element to `crosshair`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_crosshair(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::Crosshair); + self + } + + /// Sets cursor style when hovering over an element to `vertical-text`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_vertical_text(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::IBeamCursorForVerticalLayout); + self + } + + /// Sets cursor style when hovering over an element to `alias`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_alias(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::DragLink); + self + } + + /// Sets cursor style when hovering over an element to `copy`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_copy(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::DragCopy); + self + } + + /// Sets cursor style when hovering over an element to `no-drop`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_no_drop(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed); + self + } + + /// Sets cursor style when hovering over an element to `grab`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_grab(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::OpenHand); + self + } + + /// Sets cursor style when hovering over an element to `grabbing`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_grabbing(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ClosedHand); + self + } + + /// Sets cursor style when hovering over an element to `col-resize`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_col_resize(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ResizeLeftRight); + self + } + + /// Sets cursor style when hovering over an element to `row-resize`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_row_resize(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ResizeUpDown); + self + } + + /// Sets cursor style when hovering over an element to `n-resize`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_n_resize(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ResizeUp); + self + } + + /// Sets cursor style when hovering over an element to `e-resize`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_e_resize(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ResizeRight); + self + } + + /// Sets cursor style when hovering over an element to `s-resize`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_s_resize(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ResizeDown); + self + } + + /// Sets cursor style when hovering over an element to `w-resize`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_w_resize(mut self) -> Self { + self.style().mouse_cursor = Some(CursorStyle::ResizeLeft); + self + } + /// Sets the whitespace of the element to `normal`. /// [Docs](https://tailwindcss.com/docs/whitespace#normal) fn whitespace_normal(mut self) -> Self { diff --git a/crates/language_selector2/Cargo.toml b/crates/language_selector2/Cargo.toml new file mode 100644 index 0000000000..67f0d1e0ee --- /dev/null +++ b/crates/language_selector2/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "language_selector2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/language_selector.rs" +doctest = false + +[dependencies] +editor = { package = "editor2", path = "../editor2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } +language = { package = "language2", path = "../language2" } +gpui = { package = "gpui2", path = "../gpui2" } +picker = { package = "picker2", path = "../picker2" } +project = { package = "project2", path = "../project2" } +theme = { package = "theme2", path = "../theme2" } +ui = { package = "ui2", path = "../ui2" } +settings = { package = "settings2", path = "../settings2" } +util = { path = "../util" } +workspace = { package = "workspace2", path = "../workspace2" } +anyhow.workspace = true + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } diff --git a/crates/language_selector2/src/active_buffer_language.rs b/crates/language_selector2/src/active_buffer_language.rs new file mode 100644 index 0000000000..4034cb0429 --- /dev/null +++ b/crates/language_selector2/src/active_buffer_language.rs @@ -0,0 +1,82 @@ +use editor::Editor; +use gpui::{ + div, Div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView, +}; +use std::sync::Arc; +use ui::{Button, ButtonCommon, Clickable, Tooltip}; +use workspace::{item::ItemHandle, StatusItemView, Workspace}; + +use crate::LanguageSelector; + +pub struct ActiveBufferLanguage { + active_language: Option>>, + workspace: WeakView, + _observe_active_editor: Option, +} + +impl ActiveBufferLanguage { + pub fn new(workspace: &Workspace) -> Self { + Self { + active_language: None, + workspace: workspace.weak_handle(), + _observe_active_editor: None, + } + } + + fn update_language(&mut self, editor: View, cx: &mut ViewContext) { + self.active_language = Some(None); + + let editor = editor.read(cx); + if let Some((_, buffer, _)) = editor.active_excerpt(cx) { + if let Some(language) = buffer.read(cx).language() { + self.active_language = Some(Some(language.name())); + } + } + + cx.notify(); + } +} + +impl Render for ActiveBufferLanguage { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Div { + div().when_some(self.active_language.as_ref(), |el, active_language| { + let active_language_text = if let Some(active_language_text) = active_language { + active_language_text.to_string() + } else { + "Unknown".to_string() + }; + + el.child( + Button::new("change-language", active_language_text) + .on_click(cx.listener(|this, _, cx| { + if let Some(workspace) = this.workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + LanguageSelector::toggle(workspace, cx) + }); + } + })) + .tooltip(|cx| Tooltip::text("Select Language", cx)), + ) + }) + } +} + +impl StatusItemView for ActiveBufferLanguage { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) { + if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(cx)) { + self._observe_active_editor = Some(cx.observe(&editor, Self::update_language)); + self.update_language(editor, cx); + } else { + self.active_language = None; + self._observe_active_editor = None; + } + + cx.notify(); + } +} diff --git a/crates/language_selector2/src/language_selector.rs b/crates/language_selector2/src/language_selector.rs new file mode 100644 index 0000000000..49be0c5418 --- /dev/null +++ b/crates/language_selector2/src/language_selector.rs @@ -0,0 +1,231 @@ +mod active_buffer_language; + +pub use active_buffer_language::ActiveBufferLanguage; +use anyhow::anyhow; +use editor::Editor; +use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; +use gpui::{ + actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model, + ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, +}; +use language::{Buffer, LanguageRegistry}; +use picker::{Picker, PickerDelegate}; +use project::Project; +use std::sync::Arc; +use ui::{v_stack, HighlightedLabel, ListItem, Selectable}; +use util::ResultExt; +use workspace::Workspace; + +actions!(Toggle); + +pub fn init(cx: &mut AppContext) { + cx.observe_new_views(LanguageSelector::register).detach(); +} + +pub struct LanguageSelector { + picker: View>, +} + +impl LanguageSelector { + fn register(workspace: &mut Workspace, _: &mut ViewContext) { + workspace.register_action(move |workspace, _: &Toggle, cx| { + Self::toggle(workspace, cx); + }); + } + + fn toggle(workspace: &mut Workspace, cx: &mut ViewContext) -> Option<()> { + let registry = workspace.app_state().languages.clone(); + let (_, buffer, _) = workspace + .active_item(cx)? + .act_as::(cx)? + .read(cx) + .active_excerpt(cx)?; + let project = workspace.project().clone(); + + workspace.toggle_modal(cx, move |cx| { + LanguageSelector::new(buffer, project, registry, cx) + }); + Some(()) + } + + fn new( + buffer: Model, + project: Model, + language_registry: Arc, + cx: &mut ViewContext, + ) -> Self { + let delegate = LanguageSelectorDelegate::new( + cx.view().downgrade(), + buffer, + project, + language_registry, + ); + + let picker = cx.build_view(|cx| Picker::new(delegate, cx)); + Self { picker } + } +} + +impl Render for LanguageSelector { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + v_stack().min_w_96().child(self.picker.clone()) + } +} + +impl FocusableView for LanguageSelector { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.picker.focus_handle(cx) + } +} +impl EventEmitter for LanguageSelector {} + +pub struct LanguageSelectorDelegate { + language_selector: WeakView, + buffer: Model, + project: Model, + language_registry: Arc, + candidates: Vec, + matches: Vec, + selected_index: usize, +} + +impl LanguageSelectorDelegate { + fn new( + language_selector: WeakView, + buffer: Model, + project: Model, + language_registry: Arc, + ) -> Self { + let candidates = language_registry + .language_names() + .into_iter() + .enumerate() + .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name)) + .collect::>(); + + Self { + language_selector, + buffer, + project, + language_registry, + candidates, + matches: vec![], + selected_index: 0, + } + } +} + +impl PickerDelegate for LanguageSelectorDelegate { + type ListItem = ListItem; + + fn placeholder_text(&self) -> Arc { + "Select a language...".into() + } + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { + if let Some(mat) = self.matches.get(self.selected_index) { + let language_name = &self.candidates[mat.candidate_id].string; + let language = self.language_registry.language_for_name(language_name); + let project = self.project.downgrade(); + let buffer = self.buffer.downgrade(); + cx.spawn(|_, mut cx| async move { + let language = language.await?; + let project = project + .upgrade() + .ok_or_else(|| anyhow!("project was dropped"))?; + let buffer = buffer + .upgrade() + .ok_or_else(|| anyhow!("buffer was dropped"))?; + project.update(&mut cx, |project, cx| { + project.set_language_for_buffer(&buffer, language, cx); + }) + }) + .detach_and_log_err(cx); + } + self.dismissed(cx); + } + + fn dismissed(&mut self, cx: &mut ViewContext>) { + self.language_selector + .update(cx, |_, cx| cx.emit(DismissEvent)) + .log_err(); + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext>) { + self.selected_index = ix; + } + + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext>, + ) -> gpui::Task<()> { + let background = cx.background_executor().clone(); + let candidates = self.candidates.clone(); + cx.spawn(|this, mut cx| async move { + let matches = if query.is_empty() { + candidates + .into_iter() + .enumerate() + .map(|(index, candidate)| StringMatch { + candidate_id: index, + string: candidate.string, + positions: Vec::new(), + score: 0.0, + }) + .collect() + } else { + match_strings( + &candidates, + &query, + false, + 100, + &Default::default(), + background, + ) + .await + }; + + this.update(&mut cx, |this, cx| { + let delegate = &mut this.delegate; + delegate.matches = matches; + delegate.selected_index = delegate + .selected_index + .min(delegate.matches.len().saturating_sub(1)); + cx.notify(); + }) + .log_err(); + }) + } + + fn render_match( + &self, + ix: usize, + selected: bool, + cx: &mut ViewContext>, + ) -> Option { + let mat = &self.matches[ix]; + let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name()); + let mut label = mat.string.clone(); + if buffer_language_name.as_deref() == Some(mat.string.as_str()) { + label.push_str(" (current)"); + } + + Some( + ListItem::new(ix) + .inset(true) + .selected(selected) + .child(HighlightedLabel::new(label, mat.positions.clone())), + ) + } +} diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 44056dabd1..89513be8b3 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -178,6 +178,15 @@ impl Picker { } cx.notify(); } + + pub fn query(&self, cx: &AppContext) -> String { + self.editor.read(cx).text(cx) + } + + pub fn set_query(&self, query: impl Into>, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.set_text(query, cx)); + } } impl Render for Picker { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 21d64fe91f..3802039a81 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1661,14 +1661,15 @@ impl Project { path: impl Into, cx: &mut ModelContext, ) -> Task> { - let task = self.open_buffer(path, cx); + let project_path = path.into(); + let task = self.open_buffer(project_path.clone(), cx); cx.spawn_weak(|_, cx| async move { let buffer = task.await?; let project_entry_id = buffer .read_with(&cx, |buffer, cx| { File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx)) }) - .ok_or_else(|| anyhow!("no project entry"))?; + .with_context(|| format!("no project entry for {project_path:?}"))?; let buffer: &AnyModelHandle = &buffer; Ok((project_entry_id, buffer.clone())) diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 12940dd2c4..9750fe053d 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -1691,14 +1691,15 @@ impl Project { path: impl Into, cx: &mut ModelContext, ) -> Task> { - let task = self.open_buffer(path, cx); + let project_path = path.into(); + let task = self.open_buffer(project_path.clone(), cx); cx.spawn(move |_, mut cx| async move { let buffer = task.await?; let project_entry_id = buffer .update(&mut cx, |buffer, cx| { File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx)) })? - .ok_or_else(|| anyhow!("no project entry"))?; + .with_context(|| format!("no project entry for {project_path:?}"))?; let buffer: &AnyModel = &buffer; Ok((project_entry_id, buffer.clone())) diff --git a/crates/rpc2/src/rpc.rs b/crates/rpc2/src/rpc.rs index 4bf90669b2..6f35bf64bc 100644 --- a/crates/rpc2/src/rpc.rs +++ b/crates/rpc2/src/rpc.rs @@ -9,4 +9,4 @@ pub use notification::*; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 64; +pub const PROTOCOL_VERSION: u32 = 66; diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index 2d63d1d491..f7ab3ef548 100644 --- a/crates/storybook2/src/stories.rs +++ b/crates/storybook2/src/stories.rs @@ -1,4 +1,5 @@ mod auto_height_editor; +mod cursor; mod focus; mod kitchen_sink; mod picker; @@ -7,6 +8,7 @@ mod text; mod z_index; pub use auto_height_editor::*; +pub use cursor::*; pub use focus::*; pub use kitchen_sink::*; pub use picker::*; diff --git a/crates/storybook2/src/stories/cursor.rs b/crates/storybook2/src/stories/cursor.rs new file mode 100644 index 0000000000..d160fa4f4a --- /dev/null +++ b/crates/storybook2/src/stories/cursor.rs @@ -0,0 +1,112 @@ +use gpui::{Div, Render, Stateful}; +use story::Story; +use ui::prelude::*; + +pub struct CursorStory; + +impl Render for CursorStory { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + let all_cursors: [(&str, Box) -> Stateful
>); 19] = [ + ( + "cursor_default", + Box::new(|el: Stateful
| el.cursor_default()), + ), + ( + "cursor_pointer", + Box::new(|el: Stateful
| el.cursor_pointer()), + ), + ( + "cursor_text", + Box::new(|el: Stateful
| el.cursor_text()), + ), + ( + "cursor_move", + Box::new(|el: Stateful
| el.cursor_move()), + ), + ( + "cursor_not_allowed", + Box::new(|el: Stateful
| el.cursor_not_allowed()), + ), + ( + "cursor_context_menu", + Box::new(|el: Stateful
| el.cursor_context_menu()), + ), + ( + "cursor_crosshair", + Box::new(|el: Stateful
| el.cursor_crosshair()), + ), + ( + "cursor_vertical_text", + Box::new(|el: Stateful
| el.cursor_vertical_text()), + ), + ( + "cursor_alias", + Box::new(|el: Stateful
| el.cursor_alias()), + ), + ( + "cursor_copy", + Box::new(|el: Stateful
| el.cursor_copy()), + ), + ( + "cursor_no_drop", + Box::new(|el: Stateful
| el.cursor_no_drop()), + ), + ( + "cursor_grab", + Box::new(|el: Stateful
| el.cursor_grab()), + ), + ( + "cursor_grabbing", + Box::new(|el: Stateful
| el.cursor_grabbing()), + ), + ( + "cursor_col_resize", + Box::new(|el: Stateful
| el.cursor_col_resize()), + ), + ( + "cursor_row_resize", + Box::new(|el: Stateful
| el.cursor_row_resize()), + ), + ( + "cursor_n_resize", + Box::new(|el: Stateful
| el.cursor_n_resize()), + ), + ( + "cursor_e_resize", + Box::new(|el: Stateful
| el.cursor_e_resize()), + ), + ( + "cursor_s_resize", + Box::new(|el: Stateful
| el.cursor_s_resize()), + ), + ( + "cursor_w_resize", + Box::new(|el: Stateful
| el.cursor_w_resize()), + ), + ]; + + Story::container() + .flex() + .gap_1() + .child(Story::title("cursor")) + .children(all_cursors.map(|(name, apply_cursor)| { + div().gap_1().flex().text_color(gpui::white()).child( + div() + .flex() + .items_center() + .justify_center() + .id(name) + .map(apply_cursor) + .w_64() + .h_8() + .bg(gpui::red()) + .hover(|style| style.bg(gpui::blue())) + .active(|style| style.bg(gpui::green())) + .text_sm() + .child(Story::label(name)), + ) + })) + } +} diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 3cb39aa01a..ccd13cb4d8 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,6 +1,6 @@ use gpui::{ - blue, div, green, red, white, Div, InteractiveText, ParentElement, Render, Styled, StyledText, - TextRun, View, VisualContext, WindowContext, + blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render, + Styled, StyledText, View, VisualContext, WindowContext, }; use ui::v_stack; @@ -59,13 +59,11 @@ impl Render for TextStory { ))).child( InteractiveText::new( "interactive", - StyledText::new("Hello world, how is it going?").with_runs(vec![ - cx.text_style().to_run(6), - TextRun { + StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ + (6..11, HighlightStyle { background_color: Some(green()), - ..cx.text_style().to_run(5) - }, - cx.text_style().to_run(18), + ..Default::default() + }), ]), ) .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 4fe76ce878..216762060d 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -17,6 +17,7 @@ pub enum ComponentStory { Button, Checkbox, ContextMenu, + Cursor, Disclosure, Focus, Icon, @@ -40,6 +41,7 @@ impl ComponentStory { Self::Button => cx.build_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(), Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(), + Self::Cursor => cx.build_view(|_| crate::stories::CursorStory).into(), Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(), Self::Focus => FocusStory::view(cx).into(), Self::Icon => cx.build_view(|_| ui::IconStory).into(), diff --git a/crates/theme2/src/one_themes.rs b/crates/theme2/src/one_themes.rs index 2f663618a6..e1fb5f1bed 100644 --- a/crates/theme2/src/one_themes.rs +++ b/crates/theme2/src/one_themes.rs @@ -52,13 +52,13 @@ pub(crate) fn one_dark() -> Theme { element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0), element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), - element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), + element_disabled: SystemColors::default().transparent, drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0), ghost_element_background: SystemColors::default().transparent, ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0), ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), - ghost_element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), + ghost_element_disabled: SystemColors::default().transparent, text: hsla(221. / 360., 11. / 100., 86. / 100., 1.0), text_muted: hsla(218.0 / 360., 7. / 100., 46. / 100., 1.0), text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0), diff --git a/crates/ui2/src/components.rs b/crates/ui2/src/components.rs index be95fc1fab..17271de48d 100644 --- a/crates/ui2/src/components.rs +++ b/crates/ui2/src/components.rs @@ -9,6 +9,8 @@ mod keybinding; mod label; mod list; mod popover; +mod popover_menu; +mod right_click_menu; mod stack; mod tooltip; @@ -26,6 +28,8 @@ pub use keybinding::*; pub use label::*; pub use list::*; pub use popover::*; +pub use popover_menu::*; +pub use right_click_menu::*; pub use stack::*; pub use tooltip::*; diff --git a/crates/ui2/src/components/button.rs b/crates/ui2/src/components/button.rs index 085d5f376b..25e88201f4 100644 --- a/crates/ui2/src/components/button.rs +++ b/crates/ui2/src/components/button.rs @@ -1,4 +1,5 @@ mod button; +pub(self) mod button_icon; mod button_like; mod icon_button; diff --git a/crates/ui2/src/components/button/button.rs b/crates/ui2/src/components/button/button.rs index ce26ee76a5..c1262321ce 100644 --- a/crates/ui2/src/components/button/button.rs +++ b/crates/ui2/src/components/button/button.rs @@ -1,13 +1,22 @@ -use gpui::AnyView; +use gpui::{AnyView, DefiniteLength}; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle}; +use crate::{ + ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle, +}; + +use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct Button { base: ButtonLike, label: SharedString, label_color: Option, + selected_label: Option, + icon: Option, + icon_size: Option, + icon_color: Option, + selected_icon: Option, } impl Button { @@ -16,6 +25,11 @@ impl Button { base: ButtonLike::new(id), label: label.into(), label_color: None, + selected_label: None, + icon: None, + icon_size: None, + icon_color: None, + selected_icon: None, } } @@ -23,6 +37,31 @@ impl Button { self.label_color = label_color.into(); self } + + pub fn selected_label>(mut self, label: impl Into>) -> Self { + self.selected_label = label.into().map(Into::into); + self + } + + pub fn icon(mut self, icon: impl Into>) -> Self { + self.icon = icon.into(); + self + } + + pub fn icon_size(mut self, icon_size: impl Into>) -> Self { + self.icon_size = icon_size.into(); + self + } + + pub fn icon_color(mut self, icon_color: impl Into>) -> Self { + self.icon_color = icon_color.into(); + self + } + + pub fn selected_icon(mut self, icon: impl Into>) -> Self { + self.selected_icon = icon.into(); + self + } } impl Selectable for Button { @@ -49,6 +88,18 @@ impl Clickable for Button { } } +impl FixedWidth for Button { + fn width(mut self, width: DefiniteLength) -> Self { + self.base = self.base.width(width); + self + } + + fn full_width(mut self) -> Self { + self.base = self.base.full_width(); + self + } +} + impl ButtonCommon for Button { fn id(&self) -> &ElementId { self.base.id() @@ -74,18 +125,35 @@ impl RenderOnce for Button { type Rendered = ButtonLike; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { - let label_color = if self.base.disabled { + let is_disabled = self.base.disabled; + let is_selected = self.base.selected; + + let label = self + .selected_label + .filter(|_| is_selected) + .unwrap_or(self.label); + + let label_color = if is_disabled { Color::Disabled - } else if self.base.selected { + } else if is_selected { Color::Selected } else { self.label_color.unwrap_or_default() }; - self.base.child( - Label::new(self.label) - .color(label_color) - .line_height_style(LineHeightStyle::UILabel), - ) + self.base + .children(self.icon.map(|icon| { + ButtonIcon::new(icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) + .size(self.icon_size) + .color(self.icon_color) + })) + .child( + Label::new(label) + .color(label_color) + .line_height_style(LineHeightStyle::UILabel), + ) } } diff --git a/crates/ui2/src/components/button/button_icon.rs b/crates/ui2/src/components/button/button_icon.rs new file mode 100644 index 0000000000..3b2c703938 --- /dev/null +++ b/crates/ui2/src/components/button/button_icon.rs @@ -0,0 +1,84 @@ +use crate::{prelude::*, Icon, IconElement, IconSize}; + +/// An icon that appears within a button. +/// +/// Can be used as either an icon alongside a label, like in [`Button`](crate::Button), +/// or as a standalone icon, like in [`IconButton`](crate::IconButton). +#[derive(IntoElement)] +pub(super) struct ButtonIcon { + icon: Icon, + size: IconSize, + color: Color, + disabled: bool, + selected: bool, + selected_icon: Option, +} + +impl ButtonIcon { + pub fn new(icon: Icon) -> Self { + Self { + icon, + size: IconSize::default(), + color: Color::default(), + disabled: false, + selected: false, + selected_icon: None, + } + } + + pub fn size(mut self, size: impl Into>) -> Self { + if let Some(size) = size.into() { + self.size = size; + } + + self + } + + pub fn color(mut self, color: impl Into>) -> Self { + if let Some(color) = color.into() { + self.color = color; + } + + self + } + + pub fn selected_icon(mut self, icon: impl Into>) -> Self { + self.selected_icon = icon.into(); + self + } +} + +impl Disableable for ButtonIcon { + fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +impl Selectable for ButtonIcon { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +impl RenderOnce for ButtonIcon { + type Rendered = IconElement; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + let icon = self + .selected_icon + .filter(|_| self.selected) + .unwrap_or(self.icon); + + let icon_color = if self.disabled { + Color::Disabled + } else if self.selected { + Color::Selected + } else { + self.color + }; + + IconElement::new(icon).size(self.size).color(icon_color) + } +} diff --git a/crates/ui2/src/components/button/button_like.rs b/crates/ui2/src/components/button/button_like.rs index 207d59ecf1..4bef6bff77 100644 --- a/crates/ui2/src/components/button/button_like.rs +++ b/crates/ui2/src/components/button/button_like.rs @@ -1,3 +1,4 @@ +use gpui::{relative, DefiniteLength}; use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful}; use smallvec::SmallVec; @@ -5,18 +6,50 @@ use crate::h_stack; use crate::prelude::*; pub trait ButtonCommon: Clickable + Disableable { + /// A unique element ID to identify the button. fn id(&self) -> &ElementId; + + /// The visual style of the button. + /// + /// Mosty commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`] + /// for an emphasized button. fn style(self, style: ButtonStyle) -> Self; + + /// The size of the button. + /// + /// Most buttons will use the default size. + /// + /// [`ButtonSize`] can also be used to help build non-button elements + /// that are consistently sized with buttons. fn size(self, size: ButtonSize) -> Self; + + /// The tooltip that shows when a user hovers over the button. + /// + /// Nearly all interactable elements should have a tooltip. Some example + /// exceptions might a scroll bar, or a slider. fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self; } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { - #[default] + /// A filled button with a solid background color. Provides emphasis versus + /// the more common subtle button. Filled, - // Tinted, + + /// 🚧 Under construction 🚧 + /// + /// Used to emphasize a button in some way, like a selected state, or a semantic + /// coloring like an error or success button. + Tinted, + + /// The default button style, used for most buttons. Has a transparent background, + /// but has a background color to indicate states like hover and active. + #[default] Subtle, + + /// Used for buttons that only change forground color on hover and active states. + /// + /// TODO: Better docs for this. Transparent, } @@ -40,6 +73,12 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, + ButtonStyle::Tinted => ButtonLikeStyles { + background: gpui::red(), + border_color: gpui::red(), + label_color: gpui::red(), + icon_color: gpui::red(), + }, ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, border_color: transparent_black(), @@ -63,6 +102,12 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, + ButtonStyle::Tinted => ButtonLikeStyles { + background: gpui::red(), + border_color: gpui::red(), + label_color: gpui::red(), + icon_color: gpui::red(), + }, ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_hover, border_color: transparent_black(), @@ -88,6 +133,12 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, + ButtonStyle::Tinted => ButtonLikeStyles { + background: gpui::red(), + border_color: gpui::red(), + label_color: gpui::red(), + icon_color: gpui::red(), + }, ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_active, border_color: transparent_black(), @@ -114,6 +165,12 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, + ButtonStyle::Tinted => ButtonLikeStyles { + background: gpui::red(), + border_color: gpui::red(), + label_color: gpui::red(), + icon_color: gpui::red(), + }, ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, border_color: cx.theme().colors().border_focused, @@ -137,6 +194,12 @@ impl ButtonStyle { label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, + ButtonStyle::Tinted => ButtonLikeStyles { + background: gpui::red(), + border_color: gpui::red(), + label_color: gpui::red(), + icon_color: gpui::red(), + }, ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_disabled, border_color: cx.theme().colors().border_disabled, @@ -153,6 +216,8 @@ impl ButtonStyle { } } +/// ButtonSize can also be used to help build non-button elements +/// that are consistently sized with buttons. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { #[default] @@ -171,12 +236,18 @@ impl ButtonSize { } } +/// A button-like element that can be used to create a custom button when +/// prebuilt buttons are not sufficient. Use this sparingly, as it is +/// unconstrained and may make the UI feel less consistent. +/// +/// This is also used to build the prebuilt buttons. #[derive(IntoElement)] pub struct ButtonLike { id: ElementId, pub(super) style: ButtonStyle, pub(super) disabled: bool, pub(super) selected: bool, + pub(super) width: Option, size: ButtonSize, tooltip: Option AnyView>>, on_click: Option>, @@ -190,6 +261,7 @@ impl ButtonLike { style: ButtonStyle::default(), disabled: false, selected: false, + width: None, size: ButtonSize::Default, tooltip: None, children: SmallVec::new(), @@ -219,6 +291,18 @@ impl Clickable for ButtonLike { } } +impl FixedWidth for ButtonLike { + fn width(mut self, width: DefiniteLength) -> Self { + self.width = Some(width); + self + } + + fn full_width(mut self) -> Self { + self.width = Some(relative(1.)); + self + } +} + impl ButtonCommon for ButtonLike { fn id(&self) -> &ElementId { &self.id @@ -252,14 +336,19 @@ impl RenderOnce for ButtonLike { fn render(self, cx: &mut WindowContext) -> Self::Rendered { h_stack() .id(self.id.clone()) + .group("") + .flex_none() .h(self.size.height()) + .when_some(self.width, |this, width| this.w(width)) .rounded_md() - .cursor_pointer() .gap_1() .px_1() .bg(self.style.enabled(cx).background) - .hover(|hover| hover.bg(self.style.hovered(cx).background)) - .active(|active| active.bg(self.style.active(cx).background)) + .when(!self.disabled, |this| { + this.cursor_pointer() + .hover(|hover| hover.bg(self.style.hovered(cx).background)) + .active(|active| active.bg(self.style.active(cx).background)) + }) .when_some( self.on_click.filter(|_| !self.disabled), |this, on_click| { @@ -270,7 +359,11 @@ impl RenderOnce for ButtonLike { }, ) .when_some(self.tooltip, |this, tooltip| { - this.tooltip(move |cx| tooltip(cx)) + if !self.selected { + this.tooltip(move |cx| tooltip(cx)) + } else { + this + } }) .children(self.children) } diff --git a/crates/ui2/src/components/button/icon_button.rs b/crates/ui2/src/components/button/icon_button.rs index a62832059d..94431ef642 100644 --- a/crates/ui2/src/components/button/icon_button.rs +++ b/crates/ui2/src/components/button/icon_button.rs @@ -1,7 +1,9 @@ -use gpui::{Action, AnyView}; +use gpui::{Action, AnyView, DefiniteLength}; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; + +use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct IconButton { @@ -9,6 +11,7 @@ pub struct IconButton { icon: Icon, icon_size: IconSize, icon_color: Color, + selected_icon: Option, } impl IconButton { @@ -18,6 +21,7 @@ impl IconButton { icon, icon_size: IconSize::default(), icon_color: Color::Default, + selected_icon: None, } } @@ -31,6 +35,11 @@ impl IconButton { self } + pub fn selected_icon(mut self, icon: impl Into>) -> Self { + self.selected_icon = icon.into(); + self + } + pub fn action(self, action: Box) -> Self { self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone())) } @@ -60,6 +69,18 @@ impl Clickable for IconButton { } } +impl FixedWidth for IconButton { + fn width(mut self, width: DefiniteLength) -> Self { + self.base = self.base.width(width); + self + } + + fn full_width(mut self) -> Self { + self.base = self.base.full_width(); + self + } +} + impl ButtonCommon for IconButton { fn id(&self) -> &ElementId { self.base.id() @@ -85,18 +106,16 @@ impl RenderOnce for IconButton { type Rendered = ButtonLike; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { - let icon_color = if self.base.disabled { - Color::Disabled - } else if self.base.selected { - Color::Selected - } else { - self.icon_color - }; + let is_disabled = self.base.disabled; + let is_selected = self.base.selected; self.base.child( - IconElement::new(self.icon) + ButtonIcon::new(self.icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) .size(self.icon_size) - .color(icon_color), + .color(self.icon_color), ) } } diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index f071d188a1..54c8d93375 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -1,19 +1,20 @@ use crate::{ - h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader, + h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem, + ListSeparator, ListSubHeader, }; use gpui::{ - overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DismissEvent, DispatchPhase, - Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton, - MouseDownEvent, Pixels, Point, Render, View, VisualContext, + px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, + IntoElement, Render, View, VisualContext, }; use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev}; -use std::{cell::RefCell, rc::Rc}; +use std::rc::Rc; pub enum ContextMenuItem { Separator, Header(SharedString), Entry { label: SharedString, + icon: Option, handler: Rc, key_binding: Option, }, @@ -70,6 +71,7 @@ impl ContextMenu { label: label.into(), handler: Rc::new(on_click), key_binding: None, + icon: None, }); self } @@ -84,6 +86,22 @@ impl ContextMenu { label: label.into(), key_binding: KeyBinding::for_action(&*action, cx), handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), + icon: None, + }); + self + } + + pub fn link( + mut self, + label: impl Into, + action: Box, + cx: &mut WindowContext, + ) -> Self { + self.items.push(ContextMenuItem::Entry { + label: label.into(), + key_binding: KeyBinding::for_action(&*action, cx), + handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), + icon: Some(Icon::Link), }); self } @@ -176,19 +194,30 @@ impl Render for ContextMenu { ListSubHeader::new(header.clone()).into_any_element() } ContextMenuItem::Entry { - label: entry, - handler: callback, + label, + handler, key_binding, + icon, } => { - let callback = callback.clone(); + let handler = handler.clone(); let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent)); - ListItem::new(entry.clone()) + let label_element = if let Some(icon) = icon { + h_stack() + .gap_1() + .child(Label::new(label.clone())) + .child(IconElement::new(*icon)) + .into_any_element() + } else { + Label::new(label.clone()).into_any_element() + }; + + ListItem::new(label.clone()) .child( h_stack() .w_full() .justify_between() - .child(Label::new(entry.clone())) + .child(label_element) .children( key_binding .clone() @@ -197,7 +226,7 @@ impl Render for ContextMenu { ) .selected(Some(ix) == self.selected_index) .on_click(move |event, cx| { - callback(cx); + handler(cx); dismiss(event, cx) }) .into_any_element() @@ -208,174 +237,3 @@ impl Render for ContextMenu { ) } } - -pub struct MenuHandle { - id: ElementId, - child_builder: Option AnyElement + 'static>>, - menu_builder: Option View + 'static>>, - anchor: Option, - attach: Option, -} - -impl MenuHandle { - pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View + 'static) -> Self { - self.menu_builder = Some(Rc::new(f)); - self - } - - pub fn child(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self { - self.child_builder = Some(Box::new(|b| f(b).into_element().into_any())); - self - } - - /// anchor defines which corner of the menu to anchor to the attachment point - /// (by default the cursor position, but see attach) - pub fn anchor(mut self, anchor: AnchorCorner) -> Self { - self.anchor = Some(anchor); - self - } - - /// attach defines which corner of the handle to attach the menu's anchor to - pub fn attach(mut self, attach: AnchorCorner) -> Self { - self.attach = Some(attach); - self - } -} - -pub fn menu_handle(id: impl Into) -> MenuHandle { - MenuHandle { - id: id.into(), - child_builder: None, - menu_builder: None, - anchor: None, - attach: None, - } -} - -pub struct MenuHandleState { - menu: Rc>>>, - position: Rc>>, - child_layout_id: Option, - child_element: Option, - menu_element: Option, -} - -impl Element for MenuHandle { - type State = MenuHandleState; - - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (gpui::LayoutId, Self::State) { - let (menu, position) = if let Some(element_state) = element_state { - (element_state.menu, element_state.position) - } else { - (Rc::default(), Rc::default()) - }; - - let mut menu_layout_id = None; - - let menu_element = menu.borrow_mut().as_mut().map(|menu| { - let mut overlay = overlay().snap_to_window(); - if let Some(anchor) = self.anchor { - overlay = overlay.anchor(anchor); - } - overlay = overlay.position(*position.borrow()); - - let mut element = overlay.child(menu.clone()).into_any(); - menu_layout_id = Some(element.layout(cx)); - element - }); - - let mut child_element = self - .child_builder - .take() - .map(|child_builder| (child_builder)(menu.borrow().is_some())); - - let child_layout_id = child_element - .as_mut() - .map(|child_element| child_element.layout(cx)); - - let layout_id = cx.request_layout( - &gpui::Style::default(), - menu_layout_id.into_iter().chain(child_layout_id), - ); - - ( - layout_id, - MenuHandleState { - menu, - position, - child_element, - child_layout_id, - menu_element, - }, - ) - } - - fn paint( - self, - bounds: Bounds, - element_state: &mut Self::State, - cx: &mut WindowContext, - ) { - if let Some(child) = element_state.child_element.take() { - child.paint(cx); - } - - if let Some(menu) = element_state.menu_element.take() { - menu.paint(cx); - return; - } - - let Some(builder) = self.menu_builder else { - return; - }; - let menu = element_state.menu.clone(); - let position = element_state.position.clone(); - let attach = self.attach.clone(); - let child_layout_id = element_state.child_layout_id.clone(); - - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == MouseButton::Right - && bounds.contains_point(&event.position) - { - cx.stop_propagation(); - cx.prevent_default(); - - let new_menu = (builder)(cx); - let menu2 = menu.clone(); - cx.subscribe(&new_menu, move |_modal, _: &DismissEvent, cx| { - *menu2.borrow_mut() = None; - cx.notify(); - }) - .detach(); - cx.focus_view(&new_menu); - *menu.borrow_mut() = Some(new_menu); - - *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { - attach - .unwrap() - .corner(cx.layout_bounds(child_layout_id.unwrap())) - } else { - cx.mouse_position() - }; - cx.notify(); - } - }); - } -} - -impl IntoElement for MenuHandle { - type Element = Self; - - fn element_id(&self) -> Option { - Some(self.id.clone()) - } - - fn into_element(self) -> Self::Element { - self - } -} diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 12b3e57792..a993a54e15 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -27,6 +27,7 @@ pub enum Icon { Bolt, CaseSensitive, Check, + Copy, ChevronDown, ChevronLeft, ChevronRight, @@ -54,6 +55,7 @@ pub enum Icon { FolderX, Hash, InlayHint, + Link, MagicWand, MagnifyingGlass, MailOpen, @@ -99,6 +101,7 @@ impl Icon { Icon::Bolt => "icons/bolt.svg", Icon::CaseSensitive => "icons/case_insensitive.svg", Icon::Check => "icons/check.svg", + Icon::Copy => "icons/copy.svg", Icon::ChevronDown => "icons/chevron_down.svg", Icon::ChevronLeft => "icons/chevron_left.svg", Icon::ChevronRight => "icons/chevron_right.svg", @@ -126,6 +129,7 @@ impl Icon { Icon::FolderX => "icons/stop_sharing.svg", Icon::Hash => "icons/hash.svg", Icon::InlayHint => "icons/inlay_hint.svg", + Icon::Link => "icons/link.svg", Icon::MagicWand => "icons/magic-wand.svg", Icon::MagnifyingGlass => "icons/magnifying_glass.svg", Icon::MailOpen => "icons/mail-open.svg", diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index 562131a969..7aeda3e850 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -1,6 +1,8 @@ +use std::ops::Range; + use crate::prelude::*; use crate::styled_ext::StyledExt; -use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext}; +use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum LabelSize { @@ -99,38 +101,32 @@ impl RenderOnce for HighlightedLabel { fn render(self, cx: &mut WindowContext) -> Self::Rendered { let highlight_color = cx.theme().colors().text_accent; - let mut text_style = cx.text_style().clone(); let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); + let mut highlights: Vec<(Range, HighlightStyle)> = Vec::new(); - let mut runs: Vec = Vec::new(); + while let Some(start_ix) = highlight_indices.next() { + let mut end_ix = start_ix; - for (char_ix, char) in self.label.char_indices() { - let mut color = self.color.color(cx); - - if let Some(highlight_ix) = highlight_indices.peek() { - if char_ix == *highlight_ix { - color = highlight_color; - highlight_indices.next(); + loop { + end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8(); + if let Some(&next_ix) = highlight_indices.peek() { + if next_ix == end_ix { + end_ix = next_ix; + highlight_indices.next(); + continue; + } } + break; } - let last_run = runs.last_mut(); - let start_new_run = if let Some(last_run) = last_run { - if color == last_run.color { - last_run.len += char.len_utf8(); - false - } else { - true - } - } else { - true - }; - - if start_new_run { - text_style.color = color; - runs.push(text_style.to_run(char.len_utf8())) - } + highlights.push(( + start_ix..end_ix, + HighlightStyle { + color: Some(highlight_color), + ..Default::default() + }, + )); } div() @@ -150,7 +146,7 @@ impl RenderOnce for HighlightedLabel { LabelSize::Default => this.text_ui(), LabelSize::Small => this.text_ui_sm(), }) - .child(StyledText::new(self.label).with_runs(runs)) + .child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights)) } } diff --git a/crates/ui2/src/components/popover_menu.rs b/crates/ui2/src/components/popover_menu.rs new file mode 100644 index 0000000000..4b5144e7c7 --- /dev/null +++ b/crates/ui2/src/components/popover_menu.rs @@ -0,0 +1,231 @@ +use std::{cell::RefCell, rc::Rc}; + +use gpui::{ + overlay, point, px, rems, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, + Element, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseDownEvent, + ParentElement, Pixels, Point, View, VisualContext, WindowContext, +}; + +use crate::{Clickable, Selectable}; + +pub trait PopoverTrigger: IntoElement + Clickable + Selectable + 'static {} + +impl PopoverTrigger for T {} + +pub struct PopoverMenu { + id: ElementId, + child_builder: Option< + Box< + dyn FnOnce( + Rc>>>, + Option View + 'static>>, + ) -> AnyElement + + 'static, + >, + >, + menu_builder: Option View + 'static>>, + anchor: AnchorCorner, + attach: Option, + offset: Option>, +} + +impl PopoverMenu { + pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View + 'static) -> Self { + self.menu_builder = Some(Rc::new(f)); + self + } + + pub fn trigger(mut self, t: T) -> Self { + self.child_builder = Some(Box::new(|menu, builder| { + let open = menu.borrow().is_some(); + t.selected(open) + .when_some(builder, |el, builder| { + el.on_click({ + move |_, cx| { + let new_menu = (builder)(cx); + let menu2 = menu.clone(); + let previous_focus_handle = cx.focused(); + + cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { + if modal.focus_handle(cx).contains_focused(cx) { + if previous_focus_handle.is_some() { + cx.focus(&previous_focus_handle.as_ref().unwrap()) + } + } + *menu2.borrow_mut() = None; + cx.notify(); + }) + .detach(); + cx.focus_view(&new_menu); + *menu.borrow_mut() = Some(new_menu); + } + }) + }) + .into_any_element() + })); + self + } + + /// anchor defines which corner of the menu to anchor to the attachment point + /// (by default the cursor position, but see attach) + pub fn anchor(mut self, anchor: AnchorCorner) -> Self { + self.anchor = anchor; + self + } + + /// attach defines which corner of the handle to attach the menu's anchor to + pub fn attach(mut self, attach: AnchorCorner) -> Self { + self.attach = Some(attach); + self + } + + /// offset offsets the position of the content by that many pixels. + pub fn offset(mut self, offset: Point) -> Self { + self.offset = Some(offset); + self + } + + fn resolved_attach(&self) -> AnchorCorner { + self.attach.unwrap_or_else(|| match self.anchor { + AnchorCorner::TopLeft => AnchorCorner::BottomLeft, + AnchorCorner::TopRight => AnchorCorner::BottomRight, + AnchorCorner::BottomLeft => AnchorCorner::TopLeft, + AnchorCorner::BottomRight => AnchorCorner::TopRight, + }) + } + + fn resolved_offset(&self, cx: &WindowContext) -> Point { + self.offset.unwrap_or_else(|| { + // Default offset = 4px padding + 1px border + let offset = rems(5. / 16.) * cx.rem_size(); + match self.anchor { + AnchorCorner::TopRight | AnchorCorner::BottomRight => point(offset, px(0.)), + AnchorCorner::TopLeft | AnchorCorner::BottomLeft => point(-offset, px(0.)), + } + }) + } +} + +pub fn popover_menu(id: impl Into) -> PopoverMenu { + PopoverMenu { + id: id.into(), + child_builder: None, + menu_builder: None, + anchor: AnchorCorner::TopLeft, + attach: None, + offset: None, + } +} + +pub struct PopoverMenuState { + child_layout_id: Option, + child_element: Option, + child_bounds: Option>, + menu_element: Option, + menu: Rc>>>, +} + +impl Element for PopoverMenu { + type State = PopoverMenuState; + + fn layout( + &mut self, + element_state: Option, + cx: &mut WindowContext, + ) -> (gpui::LayoutId, Self::State) { + let mut menu_layout_id = None; + + let (menu, child_bounds) = if let Some(element_state) = element_state { + (element_state.menu, element_state.child_bounds) + } else { + (Rc::default(), None) + }; + + let menu_element = menu.borrow_mut().as_mut().map(|menu| { + let mut overlay = overlay().snap_to_window().anchor(self.anchor); + + if let Some(child_bounds) = child_bounds { + overlay = overlay.position( + self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx), + ); + } + + let mut element = overlay.child(menu.clone()).into_any(); + menu_layout_id = Some(element.layout(cx)); + element + }); + + let mut child_element = self + .child_builder + .take() + .map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone())); + + let child_layout_id = child_element + .as_mut() + .map(|child_element| child_element.layout(cx)); + + let layout_id = cx.request_layout( + &gpui::Style::default(), + menu_layout_id.into_iter().chain(child_layout_id), + ); + + ( + layout_id, + PopoverMenuState { + menu, + child_element, + child_layout_id, + menu_element, + child_bounds, + }, + ) + } + + fn paint( + self, + _: Bounds, + element_state: &mut Self::State, + cx: &mut WindowContext, + ) { + if let Some(child) = element_state.child_element.take() { + child.paint(cx); + } + + if let Some(child_layout_id) = element_state.child_layout_id.take() { + element_state.child_bounds = Some(cx.layout_bounds(child_layout_id)); + } + + if let Some(menu) = element_state.menu_element.take() { + menu.paint(cx); + + if let Some(child_bounds) = element_state.child_bounds { + let interactive_bounds = InteractiveBounds { + bounds: child_bounds, + stacking_order: cx.stacking_order().clone(), + }; + + // Mouse-downing outside the menu dismisses it, so we don't + // want a click on the toggle to re-open it. + cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&e.position, cx) + { + cx.stop_propagation() + } + }) + } + } + } +} + +impl IntoElement for PopoverMenu { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.id.clone()) + } + + fn into_element(self) -> Self::Element { + self + } +} diff --git a/crates/ui2/src/components/right_click_menu.rs b/crates/ui2/src/components/right_click_menu.rs new file mode 100644 index 0000000000..27c4fdab96 --- /dev/null +++ b/crates/ui2/src/components/right_click_menu.rs @@ -0,0 +1,185 @@ +use std::{cell::RefCell, rc::Rc}; + +use gpui::{ + overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, ElementId, + IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, + View, VisualContext, WindowContext, +}; + +pub struct RightClickMenu { + id: ElementId, + child_builder: Option AnyElement + 'static>>, + menu_builder: Option View + 'static>>, + anchor: Option, + attach: Option, +} + +impl RightClickMenu { + pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View + 'static) -> Self { + self.menu_builder = Some(Rc::new(f)); + self + } + + pub fn trigger(mut self, e: E) -> Self { + self.child_builder = Some(Box::new(move |_| e.into_any_element())); + self + } + + /// anchor defines which corner of the menu to anchor to the attachment point + /// (by default the cursor position, but see attach) + pub fn anchor(mut self, anchor: AnchorCorner) -> Self { + self.anchor = Some(anchor); + self + } + + /// attach defines which corner of the handle to attach the menu's anchor to + pub fn attach(mut self, attach: AnchorCorner) -> Self { + self.attach = Some(attach); + self + } +} + +pub fn right_click_menu(id: impl Into) -> RightClickMenu { + RightClickMenu { + id: id.into(), + child_builder: None, + menu_builder: None, + anchor: None, + attach: None, + } +} + +pub struct MenuHandleState { + menu: Rc>>>, + position: Rc>>, + child_layout_id: Option, + child_element: Option, + menu_element: Option, +} + +impl Element for RightClickMenu { + type State = MenuHandleState; + + fn layout( + &mut self, + element_state: Option, + cx: &mut WindowContext, + ) -> (gpui::LayoutId, Self::State) { + let (menu, position) = if let Some(element_state) = element_state { + (element_state.menu, element_state.position) + } else { + (Rc::default(), Rc::default()) + }; + + let mut menu_layout_id = None; + + let menu_element = menu.borrow_mut().as_mut().map(|menu| { + let mut overlay = overlay().snap_to_window(); + if let Some(anchor) = self.anchor { + overlay = overlay.anchor(anchor); + } + overlay = overlay.position(*position.borrow()); + + let mut element = overlay.child(menu.clone()).into_any(); + menu_layout_id = Some(element.layout(cx)); + element + }); + + let mut child_element = self + .child_builder + .take() + .map(|child_builder| (child_builder)(menu.borrow().is_some())); + + let child_layout_id = child_element + .as_mut() + .map(|child_element| child_element.layout(cx)); + + let layout_id = cx.request_layout( + &gpui::Style::default(), + menu_layout_id.into_iter().chain(child_layout_id), + ); + + ( + layout_id, + MenuHandleState { + menu, + position, + child_element, + child_layout_id, + menu_element, + }, + ) + } + + fn paint( + self, + bounds: Bounds, + element_state: &mut Self::State, + cx: &mut WindowContext, + ) { + if let Some(child) = element_state.child_element.take() { + child.paint(cx); + } + + if let Some(menu) = element_state.menu_element.take() { + menu.paint(cx); + return; + } + + let Some(builder) = self.menu_builder else { + return; + }; + let menu = element_state.menu.clone(); + let position = element_state.position.clone(); + let attach = self.attach.clone(); + let child_layout_id = element_state.child_layout_id.clone(); + + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == MouseButton::Right + && bounds.contains_point(&event.position) + { + cx.stop_propagation(); + cx.prevent_default(); + + let new_menu = (builder)(cx); + let menu2 = menu.clone(); + let previous_focus_handle = cx.focused(); + + cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { + if modal.focus_handle(cx).contains_focused(cx) { + if previous_focus_handle.is_some() { + cx.focus(&previous_focus_handle.as_ref().unwrap()) + } + } + *menu2.borrow_mut() = None; + cx.notify(); + }) + .detach(); + cx.focus_view(&new_menu); + *menu.borrow_mut() = Some(new_menu); + + *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { + attach + .unwrap() + .corner(cx.layout_bounds(child_layout_id.unwrap())) + } else { + cx.mouse_position() + }; + cx.notify(); + } + }); + } +} + +impl IntoElement for RightClickMenu { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.id.clone()) + } + + fn into_element(self) -> Self::Element { + self + } +} diff --git a/crates/ui2/src/components/stories/button.rs b/crates/ui2/src/components/stories/button.rs index 17bcd8b268..9fe4f55dcb 100644 --- a/crates/ui2/src/components/stories/button.rs +++ b/crates/ui2/src/components/stories/button.rs @@ -1,7 +1,7 @@ use gpui::{Div, Render}; use story::Story; -use crate::prelude::*; +use crate::{prelude::*, Icon}; use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -16,8 +16,22 @@ impl Render for ButtonStory { .child(Button::new("default_filled", "Click me")) .child(Story::label("Selected")) .child(Button::new("selected_filled", "Click me").selected(true)) + .child(Story::label("Selected with `selected_label`")) + .child( + Button::new("selected_label_filled", "Click me") + .selected(true) + .selected_label("I have been selected"), + ) .child(Story::label("With `label_color`")) .child(Button::new("filled_with_label_color", "Click me").color(Color::Created)) + .child(Story::label("With `icon`")) + .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit)) + .child(Story::label("Selected with `icon`")) + .child( + Button::new("filled_and_selected_with_icon", "Click me") + .selected(true) + .icon(Icon::FileGit), + ) .child(Story::label("Default (Subtle)")) .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle)) .child(Story::label("Default (Transparent)")) diff --git a/crates/ui2/src/components/stories/context_menu.rs b/crates/ui2/src/components/stories/context_menu.rs index d5fb94df4f..dd1fe8d565 100644 --- a/crates/ui2/src/components/stories/context_menu.rs +++ b/crates/ui2/src/components/stories/context_menu.rs @@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View}; use story::Story; use crate::prelude::*; -use crate::{menu_handle, ContextMenu, Label}; +use crate::{right_click_menu, ContextMenu, Label}; actions!(PrintCurrentDate, PrintBestFood); @@ -45,25 +45,13 @@ impl Render for ContextMenuStory { .flex_col() .justify_between() .child( - menu_handle("test2") - .child(|is_open| { - Label::new(if is_open { - "TOP LEFT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test2") + .trigger(Label::new("TOP LEFT")) .menu(move |cx| build_menu(cx, "top left")), ) .child( - menu_handle("test1") - .child(|is_open| { - Label::new(if is_open { - "BOTTOM LEFT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test1") + .trigger(Label::new("BOTTOM LEFT")) .anchor(AnchorCorner::BottomLeft) .attach(AnchorCorner::TopLeft) .menu(move |cx| build_menu(cx, "bottom left")), @@ -75,26 +63,14 @@ impl Render for ContextMenuStory { .flex_col() .justify_between() .child( - menu_handle("test3") - .child(|is_open| { - Label::new(if is_open { - "TOP RIGHT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test3") + .trigger(Label::new("TOP RIGHT")) .anchor(AnchorCorner::TopRight) .menu(move |cx| build_menu(cx, "top right")), ) .child( - menu_handle("test4") - .child(|is_open| { - Label::new(if is_open { - "BOTTOM RIGHT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test4") + .trigger(Label::new("BOTTOM RIGHT")) .anchor(AnchorCorner::BottomRight) .attach(AnchorCorner::TopRight) .menu(move |cx| build_menu(cx, "bottom right")), diff --git a/crates/ui2/src/components/stories/icon_button.rs b/crates/ui2/src/components/stories/icon_button.rs index 04f8ab7c16..3c4d68f8af 100644 --- a/crates/ui2/src/components/stories/icon_button.rs +++ b/crates/ui2/src/components/stories/icon_button.rs @@ -20,6 +20,14 @@ impl Render for IconButtonStory { .w_8() .child(IconButton::new("icon_a", Icon::Hash).selected(true)), ) + .child(Story::label("Selected with `selected_icon`")) + .child( + div().w_8().child( + IconButton::new("icon_a", Icon::AudioOn) + .selected(true) + .selected_icon(Icon::AudioOff), + ), + ) .child(Story::label("Disabled")) .child( div() diff --git a/crates/ui2/src/styled_ext.rs b/crates/ui2/src/styled_ext.rs index cb224fd0fe..e567830d6c 100644 --- a/crates/ui2/src/styled_ext.rs +++ b/crates/ui2/src/styled_ext.rs @@ -1,4 +1,6 @@ use gpui::{px, Styled, WindowContext}; +use settings::Settings; +use theme::ThemeSettings; use crate::prelude::*; use crate::{ElevationIndex, UITextSize}; @@ -60,6 +62,18 @@ pub trait StyledExt: Styled + Sized { self.text_size(size) } + /// The font size for buffer text. + /// + /// Retrieves the default font size, or the user's custom font size if set. + /// + /// This should only be used for text that is displayed in a buffer, + /// or other places that text needs to match the user's buffer font size. + fn text_buffer(self, cx: &mut WindowContext) -> Self { + let settings = ThemeSettings::get_global(cx); + + self.text_size(settings.buffer_font_size) + } + /// The [`Surface`](ui2::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 06fdf7f9c1..437e7c0192 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -7,8 +7,8 @@ use gpui::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use ui::prelude::*; -use ui::{h_stack, menu_handle, ContextMenu, IconButton, Tooltip}; +use ui::{h_stack, ContextMenu, IconButton, Tooltip}; +use ui::{prelude::*, right_click_menu}; pub enum PanelEvent { ChangePosition, @@ -702,7 +702,7 @@ impl Render for PanelButtons { }; Some( - menu_handle(name) + right_click_menu(name) .menu(move |cx| { const POSITIONS: [DockPosition; 3] = [ DockPosition::Left, @@ -726,14 +726,14 @@ impl Render for PanelButtons { }) .anchor(menu_anchor) .attach(menu_attach) - .child(move |_is_open| { + .trigger( IconButton::new(name, icon) .selected(is_active_button) .action(action.boxed_clone()) .tooltip(move |cx| { Tooltip::for_action(tooltip.clone(), &*action, cx) - }) - }), + }), + ), ) }); diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 4d417b6e59..63475c2aba 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -135,24 +135,22 @@ impl Workspace { } pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext) { - todo!() - // self.dismiss_notification::(toast.id, cx); - // self.show_notification(toast.id, cx, |cx| { - // cx.add_view(|_cx| match toast.on_click.as_ref() { - // Some((click_msg, on_click)) => { - // let on_click = on_click.clone(); - // simple_message_notification::MessageNotification::new(toast.msg.clone()) - // .with_click_message(click_msg.clone()) - // .on_click(move |cx| on_click(cx)) - // } - // None => simple_message_notification::MessageNotification::new(toast.msg.clone()), - // }) - // }) + self.dismiss_notification::(toast.id, cx); + self.show_notification(toast.id, cx, |cx| { + cx.build_view(|_cx| match toast.on_click.as_ref() { + Some((click_msg, on_click)) => { + let on_click = on_click.clone(); + simple_message_notification::MessageNotification::new(toast.msg.clone()) + .with_click_message(click_msg.clone()) + .on_click(move |cx| on_click(cx)) + } + None => simple_message_notification::MessageNotification::new(toast.msg.clone()), + }) + }) } pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext) { - todo!() - // self.dismiss_notification::(id, cx); + self.dismiss_notification::(id, cx); } fn dismiss_notification_internal( @@ -179,33 +177,10 @@ pub mod simple_message_notification { ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle, ViewContext, }; - use serde::Deserialize; - use std::{borrow::Cow, sync::Arc}; + use std::sync::Arc; use ui::prelude::*; use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; - #[derive(Clone, Default, Deserialize, PartialEq)] - pub struct OsOpen(pub Cow<'static, str>); - - impl OsOpen { - pub fn new>>(url: I) -> Self { - OsOpen(url.into()) - } - } - - // todo!() - // impl_actions!(message_notifications, [OsOpen]); - // - // todo!() - // pub fn init(cx: &mut AppContext) { - // cx.add_action(MessageNotification::dismiss); - // cx.add_action( - // |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext| { - // cx.platform().open_url(open_action.0.as_ref()); - // }, - // ) - // } - enum NotificationMessage { Text(SharedString), Element(fn(TextStyle, &AppContext) -> AnyElement), @@ -213,7 +188,7 @@ pub mod simple_message_notification { pub struct MessageNotification { message: NotificationMessage, - on_click: Option) + Send + Sync>>, + on_click: Option)>>, click_message: Option, } @@ -252,7 +227,7 @@ pub mod simple_message_notification { pub fn on_click(mut self, on_click: F) -> Self where - F: 'static + Send + Sync + Fn(&mut ViewContext), + F: 'static + Fn(&mut ViewContext), { self.on_click = Some(Arc::new(on_click)); self diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index f16a4fcf09..438ad39693 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -2,14 +2,15 @@ use crate::{ item::{Item, ItemHandle, ItemSettings, WeakItemHandle}, toolbar::Toolbar, workspace_settings::{AutosaveSetting, WorkspaceSettings}, - SplitDirection, Workspace, + NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter, - FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, - ViewContext, VisualContext, WeakView, WindowContext, + actions, overlay, prelude::*, rems, Action, AnchorCorner, AnyWeakView, AppContext, + AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable, + FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext, + VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath}; @@ -25,8 +26,10 @@ use std::{ }, }; -use ui::v_stack; -use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip}; +use ui::{ + h_stack, prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Label, Tooltip, +}; +use ui::{v_stack, ContextMenu}; use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -50,7 +53,7 @@ pub enum SaveIntent { //todo!("Do we need the default bound on actions? Decide soon") // #[register_action] -#[derive(Clone, Deserialize, PartialEq, Debug)] +#[derive(Action, Clone, Deserialize, PartialEq, Debug)] pub struct ActivateItem(pub usize); // #[derive(Clone, PartialEq)] @@ -143,17 +146,24 @@ impl fmt::Debug for Event { } } +struct FocusedView { + view: AnyWeakView, + focus_handle: FocusHandle, +} + pub struct Pane { focus_handle: FocusHandle, items: Vec>, activation_history: Vec, zoomed: bool, active_item_index: usize, - // last_focused_view_by_item: HashMap, + last_focused_view_by_item: HashMap, autoscroll: bool, nav_history: NavHistory, toolbar: View, - // tab_bar_context_menu: TabBarContextMenu, + tab_bar_focus_handle: FocusHandle, + new_item_menu: Option>, + split_item_menu: Option>, // tab_context_menu: ViewHandle, workspace: WeakView, project: Model, @@ -306,7 +316,7 @@ impl Pane { activation_history: Vec::new(), zoomed: false, active_item_index: 0, - // last_focused_view_by_item: Default::default(), + last_focused_view_by_item: Default::default(), autoscroll: false, nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState { mode: NavigationMode::Normal, @@ -318,6 +328,9 @@ impl Pane { next_timestamp, }))), toolbar: cx.build_view(|_| Toolbar::new()), + tab_bar_focus_handle: cx.focus_handle(), + new_item_menu: None, + split_item_menu: None, // tab_bar_context_menu: TabBarContextMenu { // kind: TabBarContextMenuKind::New, // handle: context_menu, @@ -392,9 +405,48 @@ impl Pane { } pub fn has_focus(&self, cx: &WindowContext) -> bool { + // todo!(); // inline this manually self.focus_handle.contains_focused(cx) } + fn focus_in(&mut self, cx: &mut ViewContext) { + if !self.has_focus(cx) { + cx.emit(Event::Focus); + cx.notify(); + } + + self.toolbar.update(cx, |toolbar, cx| { + toolbar.focus_changed(true, cx); + }); + + if let Some(active_item) = self.active_item() { + if self.focus_handle.is_focused(cx) { + // Pane was focused directly. We need to either focus a view inside the active item, + // or focus the active item itself + if let Some(weak_last_focused_view) = + self.last_focused_view_by_item.get(&active_item.item_id()) + { + weak_last_focused_view.focus(cx); + return; + } + + active_item.focus_handle(cx).focus(cx); + } else if !self.tab_bar_focus_handle.contains_focused(cx) { + if let Some(focused) = cx.focused() { + self.last_focused_view_by_item + .insert(active_item.item_id(), focused); + } + } + } + } + + fn focus_out(&mut self, cx: &mut ViewContext) { + self.toolbar.update(cx, |toolbar, cx| { + toolbar.focus_changed(false, cx); + }); + cx.notify(); + } + pub fn active_item_index(&self) -> usize { self.active_item_index } @@ -652,21 +704,16 @@ impl Pane { .position(|i| i.item_id() == item.item_id()) } - // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // // Potentially warn the user of the new keybinding - // let workspace_handle = self.workspace().clone(); - // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) - // .detach(); - - // if self.zoomed { - // cx.emit(Event::ZoomOut); - // } else if !self.items.is_empty() { - // if !self.has_focus { - // cx.focus_self(); - // } - // cx.emit(Event::ZoomIn); - // } - // } + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else if !self.items.is_empty() { + if !self.focus_handle.contains_focused(cx) { + cx.focus_self(); + } + cx.emit(Event::ZoomIn); + } + } pub fn activate_item( &mut self, @@ -1403,7 +1450,7 @@ impl Pane { let close_right = ItemSettings::get_global(cx).close_position.right(); let is_active = ix == self.active_item_index; - div() + let tab = div() .group("") .id(ix) .cursor_pointer() @@ -1477,51 +1524,75 @@ impl Pane { .children((!close_right).then(|| close_icon())) .child(label) .children(close_right.then(|| close_icon())), - ) + ); + + right_click_menu(ix).trigger(tab).menu(|cx| { + ContextMenu::build(cx, |menu, cx| { + menu.action( + "Close Active Item", + CloseActiveItem { save_intent: None }.boxed_clone(), + cx, + ) + .action("Close Inactive Items", CloseInactiveItems.boxed_clone(), cx) + .action("Close Clean Items", CloseCleanItems.boxed_clone(), cx) + .action( + "Close Items To The Left", + CloseItemsToTheLeft.boxed_clone(), + cx, + ) + .action( + "Close Items To The Right", + CloseItemsToTheRight.boxed_clone(), + cx, + ) + .action( + "Close All Items", + CloseAllItems { save_intent: None }.boxed_clone(), + cx, + ) + }) + }) } fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement { div() - .group("tab_bar") .id("tab_bar") + .group("tab_bar") + .track_focus(&self.tab_bar_focus_handle) .w_full() + // 30px @ 16px/rem + .h(rems(1.875)) + .overflow_hidden() .flex() + .flex_none() .bg(cx.theme().colors().tab_bar_background) // Left Side .child( - div() - .relative() - .px_1() + h_stack() + .px_2() .flex() .flex_none() - .gap_2() + .gap_1() // Nav Buttons .child( - div() - .right_0() - .flex() - .items_center() - .gap_px() - .child( - div().border().border_color(gpui::red()).child( - IconButton::new("navigate_backward", Icon::ArrowLeft) - .on_click({ - let view = cx.view().clone(); - move |_, cx| view.update(cx, Self::navigate_backward) - }) - .disabled(!self.can_navigate_backward()), - ), - ) - .child( - div().border().border_color(gpui::red()).child( - IconButton::new("navigate_forward", Icon::ArrowRight) - .on_click({ - let view = cx.view().clone(); - move |_, cx| view.update(cx, Self::navigate_backward) - }) - .disabled(!self.can_navigate_forward()), - ), - ), + div().border().border_color(gpui::red()).child( + IconButton::new("navigate_backward", Icon::ArrowLeft) + .on_click({ + let view = cx.view().clone(); + move |_, cx| view.update(cx, Self::navigate_backward) + }) + .disabled(!self.can_navigate_backward()), + ), + ) + .child( + div().border().border_color(gpui::red()).child( + IconButton::new("navigate_forward", Icon::ArrowRight) + .on_click({ + let view = cx.view().clone(); + move |_, cx| view.update(cx, Self::navigate_backward) + }) + .disabled(!self.can_navigate_forward()), + ), ), ) .child( @@ -1550,20 +1621,87 @@ impl Pane { .gap_px() .child( div() + .bg(gpui::blue()) .border() .border_color(gpui::red()) - .child(IconButton::new("plus", Icon::Plus)), + .child(IconButton::new("plus", Icon::Plus).on_click( + cx.listener(|this, _, cx| { + let menu = ContextMenu::build(cx, |menu, cx| { + menu.action("New File", NewFile.boxed_clone(), cx) + .action( + "New Terminal", + NewCenterTerminal.boxed_clone(), + cx, + ) + .action( + "New Search", + NewSearch.boxed_clone(), + cx, + ) + }); + cx.subscribe( + &menu, + |this, _, event: &DismissEvent, cx| { + this.focus(cx); + this.new_item_menu = None; + }, + ) + .detach(); + this.new_item_menu = Some(menu); + }), + )) + .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| { + el.child(Self::render_menu_overlay(new_item_menu)) + }), ) .child( div() .border() .border_color(gpui::red()) - .child(IconButton::new("split", Icon::Split)), + .child(IconButton::new("split", Icon::Split).on_click( + cx.listener(|this, _, cx| { + let menu = ContextMenu::build(cx, |menu, cx| { + menu.action( + "Split Right", + SplitRight.boxed_clone(), + cx, + ) + .action("Split Left", SplitLeft.boxed_clone(), cx) + .action("Split Up", SplitUp.boxed_clone(), cx) + .action("Split Down", SplitDown.boxed_clone(), cx) + }); + cx.subscribe( + &menu, + |this, _, event: &DismissEvent, cx| { + this.focus(cx); + this.split_item_menu = None; + }, + ) + .detach(); + this.split_item_menu = Some(menu); + }), + )) + .when_some( + self.split_item_menu.as_ref(), + |el, split_item_menu| { + el.child(Self::render_menu_overlay(split_item_menu)) + }, + ), ), ), ) } + fn render_menu_overlay(menu: &View) -> Div { + div() + .absolute() + .z_index(1) + .bottom_0() + .right_0() + .size_0() + .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone())) + } + // fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { // let theme = theme::current(cx).clone(); @@ -1962,9 +2100,23 @@ impl Render for Pane { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let this = cx.view().downgrade(); + v_stack() .key_context("Pane") .track_focus(&self.focus_handle) + .on_focus_in({ + let this = this.clone(); + move |event, cx| { + this.update(cx, |this, cx| this.focus_in(cx)).ok(); + } + }) + .on_focus_out({ + let this = this.clone(); + move |event, cx| { + this.update(cx, |this, cx| this.focus_out(cx)).ok(); + } + }) .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx))) .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx))) .on_action( @@ -1973,25 +2125,53 @@ impl Render for Pane { .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx))) .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx))) .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx))) - // cx.add_action(Pane::toggle_zoom); - // cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { - // pane.activate_item(action.0, true, true, cx); - // }); - // cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { - // pane.activate_item(pane.items.len() - 1, true, true, cx); - // }); - // cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { - // pane.activate_prev_item(true, cx); - // }); - // cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| { - // pane.activate_next_item(true, cx); - // }); - // cx.add_async_action(Pane::close_active_item); - // cx.add_async_action(Pane::close_inactive_items); - // cx.add_async_action(Pane::close_clean_items); - // cx.add_async_action(Pane::close_items_to_the_left); - // cx.add_async_action(Pane::close_items_to_the_right); - // cx.add_async_action(Pane::close_all_items); + .on_action(cx.listener(Pane::toggle_zoom)) + .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| { + pane.activate_item(action.0, true, true, cx); + })) + .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| { + pane.activate_item(pane.items.len() - 1, true, true, cx); + })) + .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| { + pane.activate_prev_item(true, cx); + })) + .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| { + pane.activate_next_item(true, cx); + })) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| { + pane.close_active_item(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| { + pane.close_inactive_items(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| { + pane.close_clean_items(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| { + pane.close_items_to_the_left(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| { + pane.close_items_to_the_right(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| { + pane.close_all_items(action, cx) + .map(|task| task.detach_and_log_err(cx)); + })) .size_full() .on_action( cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| { @@ -2004,8 +2184,11 @@ impl Render for Pane { .child(if let Some(item) = self.active_item() { div().flex().flex_1().child(item.to_any()) } else { - // todo!() - div().child("Empty Pane") + h_stack() + .items_center() + .size_full() + .justify_center() + .child(Label::new("Open a file or project to get started.").color(Color::Muted)) }) // enum MouseNavigationHandler {} diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index 8e448ae062..1bc84e0411 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -6,7 +6,7 @@ use gpui::{ WindowContext, }; use ui::prelude::*; -use ui::{h_stack, Button, Icon, IconButton}; +use ui::{h_stack, Icon, IconButton}; use util::ResultExt; pub trait StatusItemView: Render { @@ -53,39 +53,11 @@ impl Render for StatusBar { .gap_4() .child( h_stack().gap_1().child( - // TODO: Language picker + // Feedback Tool div() .border() .border_color(gpui::red()) - .child(Button::new("status_buffer_language", "Rust")), - ), - ) - .child( - h_stack() - .gap_1() - .child( - // Github tool - div() - .border() - .border_color(gpui::red()) - .child(IconButton::new("status-copilot", Icon::Copilot)), - ) - .child( - // Feedback Tool - div() - .border() - .border_color(gpui::red()) - .child(IconButton::new("status-feedback", Icon::Envelope)), - ), - ) - .child( - // Bottom Dock - h_stack().gap_1().child( - // Terminal - div() - .border() - .border_color(gpui::red()) - .child(IconButton::new("status-terminal", Icon::Terminal)), + .child(IconButton::new("status-feedback", Icon::Envelope)), ), ) .child( diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index 2088b6f4be..8c554dcd67 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -4,7 +4,7 @@ use gpui::{ ViewContext, WindowContext, }; use ui::prelude::*; -use ui::{h_stack, v_stack, ButtonLike, Color, Icon, IconButton, Label}; +use ui::{h_stack, v_stack, Icon, IconButton}; pub enum ToolbarItemEvent { ChangeLocation(ToolbarItemLocation), @@ -87,17 +87,8 @@ impl Render for Toolbar { .child( h_stack() .justify_between() - .child( - // Toolbar left side - h_stack().border().border_color(gpui::red()).p_1().child( - ButtonLike::new("breadcrumb") - .child(Label::new("crates/workspace2/src/toolbar.rs")) - .child(Label::new("›").color(Color::Muted)) - .child(Label::new("impl Render for Toolbar")) - .child(Label::new("›").color(Color::Muted)) - .child(Label::new("fn render")), - ), - ) + // Toolbar left side + .children(self.items.iter().map(|(child, _)| child.to_any())) // Toolbar right side .child( h_stack() @@ -116,7 +107,6 @@ impl Render for Toolbar { ), ), ) - .children(self.items.iter().map(|(child, _)| child.to_any())) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e8572e9e5d..5dcec2cabd 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3585,87 +3585,6 @@ fn open_items( }) } -// todo!() -// fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { -// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; -// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; -// const MESSAGE_ID: usize = 2; - -// if workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// return; -// } - -// if db::kvp::KEY_VALUE_STORE -// .read_kvp(NEW_DOCK_HINT_KEY) -// .ok() -// .flatten() -// .is_some() -// { -// if !workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// cx.update(|cx| { -// cx.update_global::(|tracker, _| { -// let entry = tracker -// .entry(TypeId::of::()) -// .or_default(); -// if !entry.contains(&MESSAGE_ID) { -// entry.push(MESSAGE_ID); -// } -// }); -// }); -// } - -// return; -// } - -// cx.spawn(|_| async move { -// db::kvp::KEY_VALUE_STORE -// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) -// .await -// .ok(); -// }) -// .detach(); - -// workspace -// .update(cx, |workspace, cx| { -// workspace.show_notification_once(2, cx, |cx| { -// cx.build_view(|_| { -// MessageNotification::new_element(|text, _| { -// Text::new( -// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", -// text, -// ) -// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { -// let code_span_background_color = settings::get::(cx) -// .theme -// .editor -// .document_highlight_read_background; - -// cx.scene().push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }) -// }) -// .into_any() -// }) -// .with_click_message("Read more about the new panel system") -// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) -// }) -// }) -// }) -// .ok(); - fn notify_if_database_failed(workspace: WindowHandle, cx: &mut AsyncAppContext) { const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; @@ -3719,6 +3638,8 @@ impl Render for Workspace { .items_start() .text_color(cx.theme().colors().text) .bg(cx.theme().colors().background) + .border() + .border_color(cx.theme().colors().border) .children(self.titlebar_item.clone()) .child( div() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7d8289e867..245bb4cd58 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -172,7 +172,7 @@ osx_info_plist_exts = ["resources/info/*"] osx_url_schemes = ["zed-dev"] [package.metadata.bundle-nightly] -icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] +icon = ["resources/app-icon-nightly@2x.png", "resources/app-icon-nightly.png"] identifier = "dev.zed.Zed-Nightly" name = "Zed Nightly" osx_minimum_system_version = "10.15.7" diff --git a/crates/zed/resources/app-icon-nightly.png b/crates/zed/resources/app-icon-nightly.png new file mode 100644 index 0000000000..35b3173478 Binary files /dev/null and b/crates/zed/resources/app-icon-nightly.png differ diff --git a/crates/zed/resources/app-icon-nightly@2x.png b/crates/zed/resources/app-icon-nightly@2x.png new file mode 100644 index 0000000000..8766bc08ae Binary files /dev/null and b/crates/zed/resources/app-icon-nightly@2x.png differ diff --git a/crates/zed/src/only_instance.rs b/crates/zed/src/only_instance.rs index 85dbd3684a..e950392d99 100644 --- a/crates/zed/src/only_instance.rs +++ b/crates/zed/src/only_instance.rs @@ -39,7 +39,7 @@ pub enum IsOnlyInstance { } pub fn ensure_only_instance() -> IsOnlyInstance { - if *db::ZED_STATELESS { + if *db::ZED_STATELESS || *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev { return IsOnlyInstance::Yes; } diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index bd2a8e5a2f..ab4b724a06 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -19,7 +19,7 @@ ai = { package = "ai2", path = "../ai2"} audio = { package = "audio2", path = "../audio2" } activity_indicator = { package = "activity_indicator2", path = "../activity_indicator2"} auto_update = { package = "auto_update2", path = "../auto_update2" } -# breadcrumbs = { path = "../breadcrumbs" } +breadcrumbs = { package = "breadcrumbs2", path = "../breadcrumbs2" } call = { package = "call2", path = "../call2" } channel = { package = "channel2", path = "../channel2" } cli = { path = "../cli" } @@ -30,7 +30,7 @@ command_palette = { package="command_palette2", path = "../command_palette2" } client = { package = "client2", path = "../client2" } # clock = { path = "../clock" } copilot = { package = "copilot2", path = "../copilot2" } -# copilot_button = { path = "../copilot_button" } +copilot_button = { package = "copilot_button2", path = "../copilot_button2" } diagnostics = { package = "diagnostics2", path = "../diagnostics2" } db = { package = "db2", path = "../db2" } editor = { package="editor2", path = "../editor2" } @@ -44,7 +44,7 @@ gpui = { package = "gpui2", path = "../gpui2" } install_cli = { package = "install_cli2", path = "../install_cli2" } journal = { package = "journal2", path = "../journal2" } language = { package = "language2", path = "../language2" } -# language_selector = { path = "../language_selector" } +language_selector = { package = "language_selector2", path = "../language_selector2" } lsp = { package = "lsp2", path = "../lsp2" } menu = { package = "menu2", path = "../menu2" } # language_tools = { path = "../language_tools" } @@ -167,7 +167,7 @@ osx_info_plist_exts = ["resources/info/*"] osx_url_schemes = ["zed-dev"] [package.metadata.bundle-nightly] -icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] +icon = ["resources/app-icon-nightly@2x.png", "resources/app-icon-nightly.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Nightly" osx_minimum_system_version = "10.15.7" diff --git a/crates/zed2/resources/app-icon-nightly.png b/crates/zed2/resources/app-icon-nightly.png new file mode 100644 index 0000000000..35b3173478 Binary files /dev/null and b/crates/zed2/resources/app-icon-nightly.png differ diff --git a/crates/zed2/resources/app-icon-nightly@2x.png b/crates/zed2/resources/app-icon-nightly@2x.png new file mode 100644 index 0000000000..8766bc08ae Binary files /dev/null and b/crates/zed2/resources/app-icon-nightly@2x.png differ diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 4c7e914e37..1cf3793fe1 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -216,7 +216,7 @@ fn main() { terminal_view::init(cx); // journal2::init(app_state.clone(), cx); - // language_selector::init(cx); + language_selector::init(cx); theme_selector::init(cx); // activity_indicator::init(cx); // language_tools::init(cx); diff --git a/crates/zed2/src/only_instance.rs b/crates/zed2/src/only_instance.rs index 85dbd3684a..e950392d99 100644 --- a/crates/zed2/src/only_instance.rs +++ b/crates/zed2/src/only_instance.rs @@ -39,7 +39,7 @@ pub enum IsOnlyInstance { } pub fn ensure_only_instance() -> IsOnlyInstance { - if *db::ZED_STATELESS { + if *db::ZED_STATELESS || *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev { return IsOnlyInstance::Yes; } diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 080f19e60d..1b9f1cc719 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -7,6 +7,7 @@ mod only_instance; mod open_listener; pub use assets::*; +use breadcrumbs::Breadcrumbs; use collections::VecDeque; use editor::{Editor, MultiBuffer}; use gpui::{ @@ -95,11 +96,11 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { if let workspace::Event::PaneAdded(pane) = event { pane.update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { - // todo!() - // let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); - // toolbar.add_item(breadcrumbs, cx); + let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace)); + toolbar.add_item(breadcrumbs, cx); let buffer_search_bar = cx.build_view(search::BufferSearchBar::new); toolbar.add_item(buffer_search_bar.clone(), cx); + // todo!() // let quick_action_bar = cx.add_view(|_| { // QuickActionBar::new(buffer_search_bar, workspace) // }); @@ -135,14 +136,14 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - // let copilot = - // cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx)); + let copilot = + cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); - // let active_buffer_language = - // cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); + let active_buffer_language = + cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); // let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx)); // let feedback_button = cx.add_view(|_| { // feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) @@ -153,8 +154,8 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { status_bar.add_left_item(activity_indicator, cx); // status_bar.add_right_item(feedback_button, cx); - // status_bar.add_right_item(copilot, cx); - // status_bar.add_right_item(active_buffer_language, cx); + status_bar.add_right_item(copilot, cx); + status_bar.add_right_item(active_buffer_language, cx); // status_bar.add_right_item(vim_mode_indicator, cx); status_bar.add_right_item(cursor_position, cx); }); diff --git a/test.rs b/test.rs deleted file mode 100644 index 36996263b0..0000000000 --- a/test.rs +++ /dev/null @@ -1,5618 +0,0 @@ -#![feature(prelude_import)] -#![allow(dead_code, unused_variables)] -#[prelude_import] -use std::prelude::rust_2021::*; -#[macro_use] -extern crate std; -use color::black; -use components::button; -use element::Element; -use frame::frame; -use gpui::{ - geometry::{rect::RectF, vector::vec2f}, - platform::WindowOptions,aa -}; -use log::LevelFilter;a -use simplelog::SimpleLogger; -use themes::{rose_pine, ThemeColors}; -use view::view;a -mod adapter { - use crate::element::AnyElement; - use crate::element::{LayoutContext, PaintContext}; - use gpui::{geometry::rect::RectF, LayoutEngine};aaaa - use util::ResultExt; - pub struct Adapter(pub(crate) AnyElement); - impl gpui::Element for Adapter {aa - type LayoutState = Option; - type PaintState = (); - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut V, - cx: &mut LayoutContext,aa - ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - cx.push_layout_engine(LayoutEngine::new()); - let node = self.0.layout(view, cx).log_err();a - if let Some(node) = node { - let layout_engine = cx.layout_engine().unwrap(); - layout_engine.compute_layout(node, constraint.max).log_err(); - } - let layout_engine = cx.pop_layout_engine(); - if true {a - if !layout_engine.is_some() { - ::core::panicking::panic("assertion failed: layout_engine.is_some()") - } - } - (constraint.max, layout_engine)a - } - fn paint(a - &mut self, - scene: &mut gpui::SceneBuilder, - bounds: RectF, - visible_bounds: RectF, - layout_engine: &mut Option, - view: &mut V, - legacy_cx: &mut gpui::PaintContext,aaa - ) -> Self::PaintState { - legacy_cx.push_layout_engine(layout_engine.take().unwrap()); - let mut cx = PaintContext::new(legacy_cx, scene); - self.0.paint(view, &mut cx).log_err(); - *layout_engine = legacy_cx.pop_layout_engine(); - if true { - if !layout_engine.is_some() { - ::core::panicking::panic("assertion failed: layout_engine.is_some()") - } - } - } - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - bounds: RectF, - visible_bounds: RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> Option { - ::core::panicking::panic("not yet implemented") - } - fn debug( - &self, - bounds: RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> gpui::serde_json::Value { - ::core::panicking::panic("not yet implemented") - } - } -} -mod color { - #![allow(dead_code)] - use smallvec::SmallVec; - use std::{num::ParseIntError, ops::Range}; - pub fn rgb>(hex: u32) -> C { - let r = ((hex >> 16) & 0xFF) as f32 / 255.0; - let g = ((hex >> 8) & 0xFF) as f32 / 255.0; - let b = (hex & 0xFF) as f32 / 255.0; - Rgba { r, g, b, a: 1.0 }.into() - } - pub struct Rgba { - pub r: f32, - pub g: f32, - pub b: f32, - pub a: f32, - } - #[automatically_derived] - impl ::core::clone::Clone for Rgba { - #[inline] - fn clone(&self) -> Rgba { - let _: ::core::clone::AssertParamIsClone; - *self - } - } - #[automatically_derived] - impl ::core::marker::Copy for Rgba {} - #[automatically_derived] - impl ::core::default::Default for Rgba { - #[inline] - fn default() -> Rgba { - Rgba { - r: ::core::default::Default::default(), - g: ::core::default::Default::default(), - b: ::core::default::Default::default(), - a: ::core::default::Default::default(), - } - } - } - #[automatically_derived] - impl ::core::fmt::Debug for Rgba { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field4_finish( - f, "Rgba", "r", &self.r, "g", &self.g, "b", &self.b, "a", &&self.a, - ) - } - } - pub trait Lerp { - fn lerp(&self, level: f32) -> Hsla; - } - impl Lerp for Range { - fn lerp(&self, level: f32) -> Hsla { - let level = level.clamp(0., 1.); - Hsla { - h: self.start.h + (level * (self.end.h - self.start.h)), - s: self.start.s + (level * (self.end.s - self.start.s)), - l: self.start.l + (level * (self.end.l - self.start.l)), - a: self.start.a + (level * (self.end.a - self.start.a)), - } - } - } - impl From for Rgba { - fn from(value: gpui::color::Color) -> Self { - Self { - r: value.0.r as f32 / 255.0, - g: value.0.g as f32 / 255.0, - b: value.0.b as f32 / 255.0, - a: value.0.a as f32 / 255.0, - } - } - } - impl From for Rgba { - fn from(color: Hsla) -> Self { - let h = color.h; - let s = color.s; - let l = color.l; - let c = (1.0 - (2.0 * l - 1.0).abs()) * s; - let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs()); - let m = l - c / 2.0; - let cm = c + m; - let xm = x + m; - let (r, g, b) = match (h * 6.0).floor() as i32 { - 0 | 6 => (cm, xm, m), - 1 => (xm, cm, m), - 2 => (m, cm, xm), - 3 => (m, xm, cm), - 4 => (xm, m, cm), - _ => (cm, m, xm), - }; - Rgba { - r, - g, - b, - a: color.a, - } - } - } - impl TryFrom<&'_ str> for Rgba { - type Error = ParseIntError; - fn try_from(value: &'_ str) -> Result { - let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0; - let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0; - let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0; - let a = if value.len() > 7 { - u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0 - } else { - 1.0 - }; - Ok(Rgba { r, g, b, a }) - } - } - impl Into for Rgba { - fn into(self) -> gpui::color::Color { - gpui::color::rgba(self.r, self.g, self.b, self.a) - } - } - pub struct Hsla { - pub h: f32, - pub s: f32, - pub l: f32, - pub a: f32, - } - #[automatically_derived] - impl ::core::default::Default for Hsla { - #[inline] - fn default() -> Hsla { - Hsla { - h: ::core::default::Default::default(), - s: ::core::default::Default::default(), - l: ::core::default::Default::default(), - a: ::core::default::Default::default(), - } - } - } - #[automatically_derived] - impl ::core::marker::Copy for Hsla {} - #[automatically_derived] - impl ::core::clone::Clone for Hsla { - #[inline] - fn clone(&self) -> Hsla { - let _: ::core::clone::AssertParamIsClone; - *self - } - } - #[automatically_derived] - impl ::core::fmt::Debug for Hsla { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field4_finish( - f, "Hsla", "h", &self.h, "s", &self.s, "l", &self.l, "a", &&self.a, - ) - } - } - #[automatically_derived] - impl ::core::marker::StructuralPartialEq for Hsla {} - #[automatically_derived] - impl ::core::cmp::PartialEq for Hsla { - #[inline] - fn eq(&self, other: &Hsla) -> bool { - self.h == other.h && self.s == other.s && self.l == other.l && self.a == other.a - } - } - pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla { - Hsla { - h: h.clamp(0., 1.), - s: s.clamp(0., 1.), - l: l.clamp(0., 1.), - a: a.clamp(0., 1.), - } - } - pub fn black() -> Hsla { - Hsla { - h: 0., - s: 0., - l: 0., - a: 1., - } - } - impl From for Hsla { - fn from(color: Rgba) -> Self { - let r = color.r; - let g = color.g; - let b = color.b; - let max = r.max(g.max(b)); - let min = r.min(g.min(b)); - let delta = max - min; - let l = (max + min) / 2.0; - let s = if l == 0.0 || l == 1.0 { - 0.0 - } else if l < 0.5 { - delta / (2.0 * l) - } else { - delta / (2.0 - 2.0 * l) - }; - let h = if delta == 0.0 { - 0.0 - } else if max == r { - ((g - b) / delta).rem_euclid(6.0) / 6.0 - } else if max == g { - ((b - r) / delta + 2.0) / 6.0 - } else { - ((r - g) / delta + 4.0) / 6.0 - }; - Hsla { - h, - s, - l, - a: color.a, - } - } - } - impl Hsla { - /// Scales the saturation and lightness by the given values, clamping at 1.0. - pub fn scale_sl(mut self, s: f32, l: f32) -> Self { - self.s = (self.s * s).clamp(0., 1.); - self.l = (self.l * l).clamp(0., 1.); - self - } - /// Increases the saturation of the color by a certain amount, with a max - /// value of 1.0. - pub fn saturate(mut self, amount: f32) -> Self { - self.s += amount; - self.s = self.s.clamp(0.0, 1.0); - self - } - /// Decreases the saturation of the color by a certain amount, with a min - /// value of 0.0. - pub fn desaturate(mut self, amount: f32) -> Self { - self.s -= amount; - self.s = self.s.max(0.0); - if self.s < 0.0 { - self.s = 0.0; - } - self - } - /// Brightens the color by increasing the lightness by a certain amount, - /// with a max value of 1.0. - pub fn brighten(mut self, amount: f32) -> Self { - self.l += amount; - self.l = self.l.clamp(0.0, 1.0); - self - } - /// Darkens the color by decreasing the lightness by a certain amount, - /// with a max value of 0.0. - pub fn darken(mut self, amount: f32) -> Self { - self.l -= amount; - self.l = self.l.clamp(0.0, 1.0); - self - } - } - impl From for Hsla { - fn from(value: gpui::color::Color) -> Self { - Rgba::from(value).into() - } - } - impl Into for Hsla { - fn into(self) -> gpui::color::Color { - Rgba::from(self).into() - } - } - pub struct ColorScale { - colors: SmallVec<[Hsla; 2]>, - positions: SmallVec<[f32; 2]>, - } - pub fn scale(colors: I) -> ColorScale - where - I: IntoIterator, - C: Into, - { - let mut scale = ColorScale { - colors: colors.into_iter().map(Into::into).collect(), - positions: SmallVec::new(), - }; - let num_colors: f32 = scale.colors.len() as f32 - 1.0; - scale.positions = (0..scale.colors.len()) - .map(|i| i as f32 / num_colors) - .collect(); - scale - } - impl ColorScale { - fn at(&self, t: f32) -> Hsla { - if true { - if !(0.0 <= t && t <= 1.0) { - { - ::core::panicking::panic_fmt(format_args!( - "t value {0} is out of range. Expected value in range 0.0 to 1.0", - t, - )); - } - } - } - let position = match self - .positions - .binary_search_by(|a| a.partial_cmp(&t).unwrap()) - { - Ok(index) | Err(index) => index, - }; - let lower_bound = position.saturating_sub(1); - let upper_bound = position.min(self.colors.len() - 1); - let lower_color = &self.colors[lower_bound]; - let upper_color = &self.colors[upper_bound]; - match upper_bound.checked_sub(lower_bound) { - Some(0) | None => *lower_color, - Some(_) => { - let interval_t = (t - self.positions[lower_bound]) - / (self.positions[upper_bound] - self.positions[lower_bound]); - let h = lower_color.h + interval_t * (upper_color.h - lower_color.h); - let s = lower_color.s + interval_t * (upper_color.s - lower_color.s); - let l = lower_color.l + interval_t * (upper_color.l - lower_color.l); - let a = lower_color.a + interval_t * (upper_color.a - lower_color.a); - Hsla { h, s, l, a } - } - } - } - } -} -mod components { - use crate::{ - element::{Element, ElementMetadata}, - frame, - text::ArcCow, - themes::rose_pine, - }; - use gpui::{platform::MouseButton, ViewContext}; - use gpui_macros::Element; - use std::{marker::PhantomData, rc::Rc}; - struct ButtonHandlers { - click: Option)>>, - } - impl Default for ButtonHandlers { - fn default() -> Self { - Self { click: None } - } - } - #[element_crate = "crate"] - pub struct Button { - metadata: ElementMetadata, - handlers: ButtonHandlers, - label: Option>, - icon: Option>, - data: Rc, - view_type: PhantomData, - } - impl crate::element::Element for Button { - type Layout = crate::element::AnyElement; - fn declared_style(&mut self) -> &mut crate::style::OptionalStyle { - &mut self.metadata.style - } - fn handlers_mut(&mut self) -> &mut Vec> { - &mut self.metadata.handlers - } - fn layout( - &mut self, - view: &mut V, - cx: &mut crate::element::LayoutContext, - ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> { - let mut element = self.render(view, cx).into_any(); - let node_id = element.layout(view, cx)?; - Ok((node_id, element)) - } - fn paint<'a>( - &mut self, - layout: crate::element::Layout<'a, Self::Layout>, - view: &mut V, - cx: &mut crate::element::PaintContext, - ) -> anyhow::Result<()> { - layout.from_element.paint(view, cx)?; - Ok(()) - } - } - impl crate::element::IntoElement for Button { - type Element = Self; - fn into_element(self) -> Self { - self - } - } - impl Button { - fn new() -> Self { - Self { - metadata: Default::default(), - handlers: ButtonHandlers::default(), - label: None, - icon: None, - data: Rc::new(()), - view_type: PhantomData, - } - } - pub fn data(self, data: D) -> Button { - Button { - metadata: Default::default(), - handlers: ButtonHandlers::default(), - label: self.label, - icon: self.icon, - data: Rc::new(data), - view_type: PhantomData, - } - } - } - impl Button { - pub fn label(mut self, label: impl Into>) -> Self { - self.label = Some(label.into()); - self - } - pub fn icon(mut self, icon: impl Into>) -> Self { - self.icon = Some(icon.into()); - self - } - pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext) + 'static) -> Self { - let data = self.data.clone(); - Element::click(self, MouseButton::Left, move |view, _, cx| { - handler(view, data.as_ref(), cx); - }) - } - } - pub fn button() -> Button { - Button::new() - } - impl Button { - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl Element { - let button = frame() - .fill(rose_pine::dawn().error(0.5)) - .h_4() - .children(self.label.clone()); - if let Some(handler) = self.handlers.click.clone() { - let data = self.data.clone(); - button.mouse_down(MouseButton::Left, move |view, event, cx| { - handler(view, data.as_ref(), cx) - }) - } else { - button - } - } - } -} -mod element { - pub use crate::paint_context::PaintContext; - use crate::{ - adapter::Adapter, - color::Hsla, - hoverable::Hoverable, - style::{Display, Fill, OptionalStyle, Overflow, Position}, - }; - use anyhow::Result; - pub use gpui::LayoutContext; - use gpui::{ - geometry::{DefinedLength, Length, OptionalPoint}, - platform::{MouseButton, MouseButtonEvent}, - EngineLayout, EventContext, RenderContext, ViewContext, - }; - use gpui_macros::tailwind_lengths; - use std::{ - any::{Any, TypeId}, - cell::Cell, - rc::Rc, - }; - pub use taffy::tree::NodeId; - pub struct Layout<'a, E: ?Sized> { - pub from_engine: EngineLayout, - pub from_element: &'a mut E, - } - pub struct ElementMetadata { - pub style: OptionalStyle, - pub handlers: Vec>, - } - pub struct EventHandler { - handler: Rc)>, - event_type: TypeId, - outside_bounds: bool, - } - impl Clone for EventHandler { - fn clone(&self) -> Self { - Self { - handler: self.handler.clone(), - event_type: self.event_type, - outside_bounds: self.outside_bounds, - } - } - } - impl Default for ElementMetadata { - fn default() -> Self { - Self { - style: OptionalStyle::default(), - handlers: Vec::new(), - } - } - } - pub trait Element: 'static { - type Layout: 'static; - fn declared_style(&mut self) -> &mut OptionalStyle; - fn computed_style(&mut self) -> &OptionalStyle { - self.declared_style() - } - fn handlers_mut(&mut self) -> &mut Vec>; - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result<(NodeId, Self::Layout)>; - fn paint<'a>( - &mut self, - layout: Layout, - view: &mut V, - cx: &mut PaintContext, - ) -> Result<()>; - /// Convert to a dynamically-typed element suitable for layout and paint. - fn into_any(self) -> AnyElement - where - Self: 'static + Sized, - { - AnyElement { - element: Box::new(self) as Box>, - layout: None, - } - } - fn adapt(self) -> Adapter - where - Self: Sized, - Self: Element, - { - Adapter(self.into_any()) - } - fn click( - self, - button: MouseButton, - handler: impl Fn(&mut V, &MouseButtonEvent, &mut ViewContext) + 'static, - ) -> Self - where - Self: Sized, - { - let pressed: Rc> = Default::default(); - self.mouse_down(button, { - let pressed = pressed.clone(); - move |_, _, _| { - pressed.set(true); - } - }) - .mouse_up_outside(button, { - let pressed = pressed.clone(); - move |_, _, _| { - pressed.set(false); - } - }) - .mouse_up(button, move |view, event, event_cx| { - if pressed.get() { - pressed.set(false); - handler(view, event, event_cx); - } - }) - } - fn mouse_down( - mut self, - button: MouseButton, - handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self - where - Self: Sized, - { - self.handlers_mut().push(EventHandler { - handler: Rc::new(move |view, event, event_cx| { - let event = event.downcast_ref::().unwrap(); - if event.button == button && event.is_down { - handler(view, event, event_cx); - } - }), - event_type: TypeId::of::(), - outside_bounds: false, - }); - self - } - fn mouse_down_outside( - mut self, - button: MouseButton, - handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self - where - Self: Sized, - { - self.handlers_mut().push(EventHandler { - handler: Rc::new(move |view, event, event_cx| { - let event = event.downcast_ref::().unwrap(); - if event.button == button && event.is_down { - handler(view, event, event_cx); - } - }), - event_type: TypeId::of::(), - outside_bounds: true, - }); - self - } - fn mouse_up( - mut self, - button: MouseButton, - handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self - where - Self: Sized, - { - self.handlers_mut().push(EventHandler { - handler: Rc::new(move |view, event, event_cx| { - let event = event.downcast_ref::().unwrap(); - if event.button == button && !event.is_down { - handler(view, event, event_cx); - } - }), - event_type: TypeId::of::(), - outside_bounds: false, - }); - self - } - fn mouse_up_outside( - mut self, - button: MouseButton, - handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self - where - Self: Sized, - { - self.handlers_mut().push(EventHandler { - handler: Rc::new(move |view, event, event_cx| { - let event = event.downcast_ref::().unwrap(); - if event.button == button && !event.is_down { - handler(view, event, event_cx); - } - }), - event_type: TypeId::of::(), - outside_bounds: true, - }); - self - } - fn block(mut self) -> Self - where - Self: Sized, - { - self.declared_style().display = Some(Display::Block); - self - } - fn flex(mut self) -> Self - where - Self: Sized, - { - self.declared_style().display = Some(Display::Flex); - self - } - fn grid(mut self) -> Self - where - Self: Sized, - { - self.declared_style().display = Some(Display::Grid); - self - } - fn overflow_visible(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow = OptionalPoint { - x: Some(Overflow::Visible), - y: Some(Overflow::Visible), - }; - self - } - fn overflow_hidden(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow = OptionalPoint { - x: Some(Overflow::Hidden), - y: Some(Overflow::Hidden), - }; - self - } - fn overflow_scroll(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow = OptionalPoint { - x: Some(Overflow::Scroll), - y: Some(Overflow::Scroll), - }; - self - } - fn overflow_x_visible(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow.x = Some(Overflow::Visible); - self - } - fn overflow_x_hidden(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow.x = Some(Overflow::Hidden); - self - } - fn overflow_x_scroll(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow.x = Some(Overflow::Scroll); - self - } - fn overflow_y_visible(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow.y = Some(Overflow::Visible); - self - } - fn overflow_y_hidden(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow.y = Some(Overflow::Hidden); - self - } - fn overflow_y_scroll(mut self) -> Self - where - Self: Sized, - { - self.declared_style().overflow.y = Some(Overflow::Scroll); - self - } - fn relative(mut self) -> Self - where - Self: Sized, - { - self.declared_style().position = Some(Position::Relative); - self - } - fn absolute(mut self) -> Self - where - Self: Sized, - { - self.declared_style().position = Some(Position::Absolute); - self - } - fn inset_0(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(0.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_px(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(1.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_0_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.125).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.25).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.375).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.5).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.625).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_3(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.75).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_3_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.875).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_4(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.25).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_6(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.5).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_7(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.75).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_8(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_9(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.25).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_10(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.5).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_11(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.75).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_12(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_14(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.5).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_16(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(4.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_20(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(5.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_24(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(6.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_28(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(7.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_32(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(8.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_36(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(9.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_40(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(10.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_44(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(11.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_48(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(12.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_52(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(13.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_56(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(14.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_60(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(15.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_64(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(16.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_72(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(18.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_80(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(20.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_96(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(24.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_half(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_3_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(20.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(40.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_3_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(60.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_4_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(80.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_3_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_4_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_5_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_1_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(8.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_2_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_3_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_4_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_5_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(41.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_6_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_7_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(58.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_8_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_9_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_10_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_11_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(91.666667).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn inset_full(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(100.).into(); - { - let inset = self - .computed_style() - .inset - .get_or_insert_with(Default::default); - inset.top = length; - inset.right = length; - inset.bottom = length; - inset.left = length; - self - } - } - fn w(mut self, width: impl Into) -> Self - where - Self: Sized, - { - self.declared_style().size.width = Some(width.into()); - self - } - fn w_auto(mut self) -> Self - where - Self: Sized, - { - self.declared_style().size.width = Some(Length::Auto); - self - } - fn w_0(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(0.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_px(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(1.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_0_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.125).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.25).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.375).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.5).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.625).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_3(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.75).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_3_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.875).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_4(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.25).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_6(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.5).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_7(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.75).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_8(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_9(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.25).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_10(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.5).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_11(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.75).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_12(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_14(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.5).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_16(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(4.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_20(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(5.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_24(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(6.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_28(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(7.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_32(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(8.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_36(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(9.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_40(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(10.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_44(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(11.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_48(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(12.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_52(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(13.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_56(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(14.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_60(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(15.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_64(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(16.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_72(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(18.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_80(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(20.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_96(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(24.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_half(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_3_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(20.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(40.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_3_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(60.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_4_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(80.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_3_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_4_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_5_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_1_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(8.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_2_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_3_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_4_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_5_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(41.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_6_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_7_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(58.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_8_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_9_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_10_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_11_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(91.666667).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn w_full(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(100.).into(); - { - self.declared_style().size.width = Some(length); - self - } - } - fn min_w_0(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(0.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_px(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(1.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_0_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.125).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.25).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.375).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.5).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.625).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_3(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.75).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_3_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.875).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_4(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.25).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_6(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.5).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_7(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.75).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_8(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_9(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.25).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_10(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.5).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_11(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.75).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_12(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_14(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.5).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_16(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(4.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_20(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(5.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_24(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(6.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_28(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(7.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_32(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(8.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_36(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(9.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_40(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(10.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_44(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(11.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_48(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(12.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_52(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(13.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_56(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(14.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_60(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(15.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_64(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(16.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_72(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(18.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_80(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(20.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_96(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(24.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_half(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_3_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(20.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(40.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_3_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(60.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_4_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(80.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_3_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_4_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_5_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_1_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(8.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_2_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_3_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_4_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_5_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(41.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_6_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_7_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(58.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_8_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_9_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_10_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_11_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(91.666667).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn min_w_full(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(100.).into(); - { - self.declared_style().min_size.width = Some(length); - self - } - } - fn h(mut self, height: impl Into) -> Self - where - Self: Sized, - { - self.declared_style().size.height = Some(height.into()); - self - } - fn h_auto(mut self) -> Self - where - Self: Sized, - { - self.declared_style().size.height = Some(Length::Auto); - self - } - fn h_0(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Pixels(0.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_px(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Pixels(1.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_0_5(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.125).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.25).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1_5(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.375).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.5).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2_5(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.625).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_3(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.75).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_3_5(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(0.875).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_4(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(1.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_5(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(1.25).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_6(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(1.5).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_7(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(1.75).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_8(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(2.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_9(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(2.25).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_10(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(2.5).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_11(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(2.75).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_12(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(3.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_14(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(3.5).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_16(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(4.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_20(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(5.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_24(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(6.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_28(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(7.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_32(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(8.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_36(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(9.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_40(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(10.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_44(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(11.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_48(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(12.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_52(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(13.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_56(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(14.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_60(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(15.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_64(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(16.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_72(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(18.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_80(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(20.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_96(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Rems(24.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_half(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1_3rd(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2_3rd(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1_4th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(25.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2_4th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_3_4th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(75.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1_5th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(20.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2_5th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(40.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_3_5th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(60.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_4_5th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(80.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1_6th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2_6th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_3_6th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_4_6th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_5_6th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_1_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(8.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_2_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_3_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(25.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_4_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_5_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(41.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_6_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(50.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_7_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(58.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_8_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_9_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(75.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_10_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_11_12th(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(91.666667).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn h_full(mut self) -> Self - where - Self: Sized, - { - let height = DefinedLength::Percent(100.).into(); - { - self.declared_style().size.height = Some(height); - self - } - } - fn min_h_0(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(0.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_px(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Pixels(1.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_0_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.125).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.25).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.375).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.5).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.625).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_3(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.75).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_3_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(0.875).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_4(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_5(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.25).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_6(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.5).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_7(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(1.75).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_8(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_9(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.25).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_10(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.5).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_11(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(2.75).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_12(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_14(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(3.5).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_16(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(4.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_20(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(5.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_24(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(6.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_28(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(7.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_32(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(8.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_36(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(9.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_40(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(10.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_44(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(11.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_48(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(12.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_52(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(13.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_56(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(14.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_60(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(15.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_64(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(16.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_72(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(18.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_80(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(20.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_96(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Rems(24.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_half(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2_3rd(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_3_4th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(20.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(40.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_3_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(60.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_4_5th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(80.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_3_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_4_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_5_6th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_1_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(8.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_2_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(16.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_3_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(25.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_4_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(33.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_5_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(41.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_6_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(50.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_7_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(58.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_8_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(66.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_9_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(75.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_10_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(83.333333).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_11_12th(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(91.666667).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn min_h_full(mut self) -> Self - where - Self: Sized, - { - let length = DefinedLength::Percent(100.).into(); - { - self.declared_style().min_size.height = Some(length); - self - } - } - fn hoverable(self) -> Hoverable - where - Self: Sized, - { - Hoverable::new(self) - } - fn fill(mut self, fill: impl Into) -> Self - where - Self: Sized, - { - self.declared_style().fill = Some(Some(fill.into())); - self - } - fn text_color(mut self, color: impl Into) -> Self - where - Self: Sized, - { - self.declared_style().text_color = Some(Some(color.into())); - self - } - } - trait ElementObject { - fn style(&mut self) -> &mut OptionalStyle; - fn handlers_mut(&mut self) -> &mut Vec>; - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result<(NodeId, Box)>; - fn paint( - &mut self, - layout: Layout, - view: &mut V, - cx: &mut PaintContext, - ) -> Result<()>; - } - impl> ElementObject for E { - fn style(&mut self) -> &mut OptionalStyle { - Element::declared_style(self) - } - fn handlers_mut(&mut self) -> &mut Vec> { - Element::handlers_mut(self) - } - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result<(NodeId, Box)> { - let (node_id, layout) = self.layout(view, cx)?; - let layout = Box::new(layout) as Box; - Ok((node_id, layout)) - } - fn paint( - &mut self, - layout: Layout, - view: &mut V, - cx: &mut PaintContext, - ) -> Result<()> { - let layout = Layout { - from_engine: layout.from_engine, - from_element: layout.from_element.downcast_mut::().unwrap(), - }; - self.paint(layout, view, cx) - } - } - /// A dynamically typed element. - pub struct AnyElement { - element: Box>, - layout: Option<(NodeId, Box)>, - } - impl AnyElement { - pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { - let pushed_text_style = self.push_text_style(cx); - let (node_id, layout) = self.element.layout(view, cx)?; - self.layout = Some((node_id, layout)); - if pushed_text_style { - cx.pop_text_style(); - } - Ok(node_id) - } - pub fn push_text_style(&mut self, cx: &mut impl RenderContext) -> bool { - let text_style = self.element.style().text_style(); - if let Some(text_style) = text_style { - let mut current_text_style = cx.text_style(); - text_style.apply(&mut current_text_style); - cx.push_text_style(current_text_style); - true - } else { - false - } - } - pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext) -> Result<()> { - let pushed_text_style = self.push_text_style(cx); - let (layout_node_id, element_layout) = - self.layout.as_mut().expect("paint called before layout"); - let layout = Layout { - from_engine: cx - .layout_engine() - .unwrap() - .computed_layout(*layout_node_id) - .expect("make sure you're using this within a gpui2 adapter element"), - from_element: element_layout.as_mut(), - }; - let style = self.element.style(); - let fill_color = style.fill.flatten().and_then(|fill| fill.color()); - if let Some(fill_color) = fill_color { - cx.scene.push_quad(gpui::scene::Quad { - bounds: layout.from_engine.bounds, - background: Some(fill_color.into()), - border: Default::default(), - corner_radii: Default::default(), - }); - } - for event_handler in self.element.handlers_mut().iter().cloned() { - let EngineLayout { order, bounds } = layout.from_engine; - let view_id = cx.view_id(); - let view_event_handler = event_handler.handler.clone(); - cx.scene - .interactive_regions - .push(gpui::scene::InteractiveRegion { - order, - bounds, - outside_bounds: event_handler.outside_bounds, - event_handler: Rc::new(move |view, event, window_cx, view_id| { - let mut view_context = ViewContext::mutable(window_cx, view_id); - let mut event_context = EventContext::new(&mut view_context); - view_event_handler( - view.downcast_mut().unwrap(), - event, - &mut event_context, - ); - }), - event_type: event_handler.event_type, - view_id, - }); - } - self.element.paint(layout, view, cx)?; - if pushed_text_style { - cx.pop_text_style(); - } - Ok(()) - } - } - impl Element for AnyElement { - type Layout = (); - fn declared_style(&mut self) -> &mut OptionalStyle { - self.element.style() - } - fn handlers_mut(&mut self) -> &mut Vec> { - self.element.handlers_mut() - } - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result<(NodeId, Self::Layout)> { - Ok((self.layout(view, cx)?, ())) - } - fn paint( - &mut self, - layout: Layout<()>, - view: &mut V, - cx: &mut PaintContext, - ) -> Result<()> { - self.paint(view, cx) - } - } - pub trait IntoElement { - type Element: Element; - fn into_element(self) -> Self::Element; - fn into_any_element(self) -> AnyElement - where - Self: Sized, - { - self.into_element().into_any() - } - } -} -mod frame { - use crate::{ - element::{ - AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId, - PaintContext, - }, - style::{OptionalStyle, Style}, - }; - use anyhow::{anyhow, Result}; - use gpui::LayoutNodeId; - use gpui_macros::IntoElement; - #[element_crate = "crate"] - pub struct Frame { - style: OptionalStyle, - handlers: Vec>, - children: Vec>, - } - impl crate::element::IntoElement for Frame { - type Element = Self; - fn into_element(self) -> Self { - self - } - } - pub fn frame() -> Frame { - Frame { - style: OptionalStyle::default(), - handlers: Vec::new(), - children: Vec::new(), - } - } - impl Element for Frame { - type Layout = (); - fn declared_style(&mut self) -> &mut OptionalStyle { - &mut self.style - } - fn handlers_mut(&mut self) -> &mut Vec> { - &mut self.handlers - } - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result<(NodeId, Self::Layout)> { - let child_layout_node_ids = self - .children - .iter_mut() - .map(|child| child.layout(view, cx)) - .collect::>>()?; - let rem_size = cx.rem_pixels(); - let style: Style = self.style.into(); - let node_id = cx - .layout_engine() - .ok_or_else(|| { - ::anyhow::__private::must_use({ - let error = - ::anyhow::__private::format_err(format_args!("no layout engine")); - error - }) - })? - .add_node(style.to_taffy(rem_size), child_layout_node_ids)?; - Ok((node_id, ())) - } - fn paint( - &mut self, - layout: Layout<()>, - view: &mut V, - cx: &mut PaintContext, - ) -> Result<()> { - for child in &mut self.children { - child.paint(view, cx)?; - } - Ok(()) - } - } - impl Frame { - pub fn child(mut self, child: impl IntoElement) -> Self { - self.children.push(child.into_any_element()); - self - } - pub fn children(mut self, children: I) -> Self - where - I: IntoIterator, - E: IntoElement, - { - self.children - .extend(children.into_iter().map(|e| e.into_any_element())); - self - } - } -} -mod hoverable { - use crate::{ - element::Element, - style::{OptionalStyle, Style}, - }; - use gpui::{ - geometry::{rect::RectF, vector::Vector2F}, - scene::MouseMove, - EngineLayout, - }; - use std::{cell::Cell, marker::PhantomData, rc::Rc}; - pub struct Hoverable { - hover_style: OptionalStyle, - computed_style: Option