Merge branch 'main' into welcome2
14
Cargo.lock
generated
|
@ -9487,6 +9487,7 @@ dependencies = [
|
||||||
"settings2",
|
"settings2",
|
||||||
"smol",
|
"smol",
|
||||||
"theme2",
|
"theme2",
|
||||||
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace2",
|
||||||
]
|
]
|
||||||
|
@ -10198,6 +10199,15 @@ dependencies = [
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-uiua"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"tree-sitter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-vue"
|
name = "tree-sitter-vue"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -11085,6 +11095,7 @@ dependencies = [
|
||||||
"settings2",
|
"settings2",
|
||||||
"theme2",
|
"theme2",
|
||||||
"theme_selector2",
|
"theme_selector2",
|
||||||
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace2",
|
||||||
]
|
]
|
||||||
|
@ -11660,6 +11671,7 @@ dependencies = [
|
||||||
"tree-sitter-svelte",
|
"tree-sitter-svelte",
|
||||||
"tree-sitter-toml",
|
"tree-sitter-toml",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
|
"tree-sitter-uiua",
|
||||||
"tree-sitter-vue",
|
"tree-sitter-vue",
|
||||||
"tree-sitter-yaml",
|
"tree-sitter-yaml",
|
||||||
"unindent",
|
"unindent",
|
||||||
|
@ -11695,6 +11707,7 @@ dependencies = [
|
||||||
"auto_update2",
|
"auto_update2",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"call2",
|
"call2",
|
||||||
|
"channel2",
|
||||||
"chrono",
|
"chrono",
|
||||||
"cli",
|
"cli",
|
||||||
"client2",
|
"client2",
|
||||||
|
@ -11783,6 +11796,7 @@ dependencies = [
|
||||||
"tree-sitter-svelte",
|
"tree-sitter-svelte",
|
||||||
"tree-sitter-toml",
|
"tree-sitter-toml",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
|
"tree-sitter-uiua",
|
||||||
"tree-sitter-vue",
|
"tree-sitter-vue",
|
||||||
"tree-sitter-yaml",
|
"tree-sitter-yaml",
|
||||||
"unindent",
|
"unindent",
|
||||||
|
|
|
@ -197,6 +197,7 @@ tree-sitter-lua = "0.0.14"
|
||||||
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
||||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
|
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
|
||||||
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
|
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
|
||||||
|
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
|
||||||
|
|
3
assets/icons/arrow_down.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.00001 12L3.5 7.50001M8.00001 12L12.5 7.50001M8.00001 12L8.00001 3.00001" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 272 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3.125 6.99344L6.35938 3.63281M3.125 6.99344L6.35938 10.3672M3.125 6.99344H11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 275 B After Width: | Height: | Size: 248 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10.8906 7.00125L7.64062 3.64062M10.8906 7.00125L7.64062 10.375M10.8906 7.00125H3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12.5 7.5L8 12M12.5 7.5L8 3M12.5 7.5L3.5 7.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 242 B |
3
assets/icons/arrow_up.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.99999 3.00001L12.5 7.50001M7.99999 3.00001L3.49999 7.50001M7.99999 3.00001L7.99999 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 286 B |
3
assets/icons/command.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 3.625C11 2.86561 11.6156 2.25 12.375 2.25C13.1344 2.25 13.75 2.86561 13.75 3.625C13.75 4.38401 13.135 4.99939 12.3761 5C12.3758 5 12.3754 5 12.375 5H11V3.625ZM9.75 5V3.625C9.75 2.17525 10.9253 1 12.375 1C13.8247 1 15 2.17525 15 3.625C15 4.98872 13.9601 6.10955 12.63 6.23777V6.25H12.3766C12.376 6.25 12.3755 6.25 12.375 6.25H11V9.75H12.375C13.8247 9.75 15 10.9253 15 12.375C15 13.8247 13.8247 15 12.375 15C11.0113 15 9.89045 13.9601 9.76223 12.63H9.75V12.3773L9.75 12.375V11H6.25V12.375C6.25 13.8247 5.07475 15 3.625 15C2.17525 15 1 13.8247 1 12.375C1 11.0113 2.03991 9.89045 3.37 9.76223V9.75H3.62274L3.625 9.75H5L5 6.25H3.625C2.17525 6.25 1 5.07475 1 3.625C1 2.17525 2.17525 1 3.625 1C4.98872 1 6.10955 2.03991 6.23777 3.37H6.25L6.25 5L9.75 5ZM9.75 6.25L6.25 6.25L6.25 9.75H9.75V6.25ZM3.625 11H5V12.375C5 13.1344 4.38439 13.75 3.625 13.75C2.86561 13.75 2.25 13.1344 2.25 12.375C2.25 11.6162 2.86472 11.0009 3.62336 11L3.625 11ZM11 12.3766C11.0009 13.1353 11.6162 13.75 12.375 13.75C13.1344 13.75 13.75 13.1344 13.75 12.375C13.75 11.6156 13.1344 11 12.375 11H11V12.375L11 12.3766ZM3.625 5C2.86561 5 2.25 4.38439 2.25 3.625C2.25 2.86561 2.86561 2.25 3.625 2.25C4.38439 2.25 5 2.86561 5 3.625V5H3.625Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
assets/icons/control.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.5 6.12488L7.64656 1.97853C7.84183 1.78328 8.1584 1.78329 8.35366 1.97854L12.5 6.12488" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 262 B |
3
assets/icons/option.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.35606 1.005H1.62545C1.28002 1.005 1 1.28502 1 1.63044C1 1.97587 1.28002 2.25589 1.62545 2.25589L5.35606 2.25589C5.62311 2.25589 5.8607 2.42545 5.94752 2.67799L9.75029 13.7387C10.0108 14.4963 10.7235 15.005 11.5247 15.005H14.3746C14.72 15.005 15 14.725 15 14.3796C15 14.0341 14.72 13.7541 14.3746 13.7541H11.5247C11.2576 13.7541 11.02 13.5845 10.9332 13.332L7.13046 2.27128C6.86998 1.51366 6.15721 1.005 5.35606 1.005ZM14.3745 1.005H9.75125C9.40582 1.005 9.1258 1.28502 9.1258 1.63044C9.1258 1.97587 9.40582 2.25589 9.75125 2.25589L14.3745 2.25589C14.72 2.25589 15 1.97587 15 1.63044C15 1.28502 14.72 1.005 14.3745 1.005Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 792 B |
3
assets/icons/return.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.375 1.63C8.375 1.28482 8.65482 1.005 9 1.005H12.375C13.8247 1.005 15 2.18025 15 3.63V7.625C15 9.07474 13.8247 10.25 12.375 10.25H3.13388L6.07194 13.1881C6.31602 13.4321 6.31602 13.8279 6.07194 14.0719C5.82786 14.316 5.43214 14.316 5.18806 14.0719L1.18306 10.0669C0.938981 9.82286 0.938981 9.42714 1.18306 9.18306L5.18306 5.18306C5.42714 4.93898 5.82286 4.93898 6.06694 5.18306C6.31102 5.42714 6.31102 5.82286 6.06694 6.06694L3.13388 9H12.375C13.1344 9 13.75 8.38439 13.75 7.625V3.63C13.75 2.87061 13.1344 2.255 12.375 2.255H9C8.65482 2.255 8.375 1.97518 8.375 1.63Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 737 B |
3
assets/icons/shift.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.46475 7.99652L7.85304 2.15921C7.93223 2.07342 8.06777 2.07341 8.14696 2.15921L13.5352 7.99652C13.7126 8.18869 13.5763 8.5 13.3148 8.5H10.5V13.7C10.5 13.8657 10.3657 14 10.2 14H5.8C5.63431 14 5.5 13.8657 5.5 13.7V8.5H2.6852C2.42367 8.5 2.28737 8.18869 2.46475 7.99652Z" stroke="black" stroke-width="1.25"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 421 B |
|
@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting {
|
||||||
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
|
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
|
||||||
AutoUpdateSetting::register(cx);
|
AutoUpdateSetting::register(cx);
|
||||||
|
|
||||||
cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {
|
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||||
wokrspace
|
workspace
|
||||||
.register_action(|_, action: &Check, cx| check(action, cx))
|
.register_action(|_, action: &Check, cx| check(action, cx))
|
||||||
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
|
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
|
||||||
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
|
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
|
||||||
|
@ -94,6 +94,11 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @nate - code to trigger update notification on launch
|
||||||
|
// workspace.show_notification(0, _cx, |cx| {
|
||||||
|
// cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
|
||||||
|
// });
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
|
pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
|
||||||
if let Some(auto_updater) = AutoUpdater::get(cx) {
|
if let Some(auto_updater) = AutoUpdater::get(cx) {
|
||||||
let auto_updater = auto_updater.read(cx);
|
let auto_updater = auto_updater.read(cx);
|
||||||
let server_url = &auto_updater.server_url;
|
let server_url = &auto_updater.server_url;
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, DismissEvent, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext,
|
div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
|
||||||
|
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
|
||||||
};
|
};
|
||||||
use menu::Cancel;
|
use util::channel::ReleaseChannel;
|
||||||
|
use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
|
||||||
|
|
||||||
pub struct UpdateNotification {
|
pub struct UpdateNotification {
|
||||||
_version: SemanticVersion,
|
version: SemanticVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for UpdateNotification {}
|
impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||||
|
@ -12,77 +14,43 @@ impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||||
impl Render for UpdateNotification {
|
impl Render for UpdateNotification {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
div().child("Updated zed!")
|
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||||
// let theme = theme::current(cx).clone();
|
|
||||||
// let theme = &theme.update_notification;
|
|
||||||
|
|
||||||
// let app_name = cx.global::<ReleaseChannel>().display_name();
|
v_stack()
|
||||||
|
.elevation_3(cx)
|
||||||
// MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
|
.p_4()
|
||||||
// Flex::column()
|
.child(
|
||||||
// .with_child(
|
h_stack()
|
||||||
// Flex::row()
|
.justify_between()
|
||||||
// .with_child(
|
.child(Label::new(format!(
|
||||||
// Text::new(
|
"Updated to {app_name} {}",
|
||||||
// format!("Updated to {app_name} {}", self.version),
|
self.version
|
||||||
// theme.message.text.clone(),
|
)))
|
||||||
// )
|
.child(
|
||||||
// .contained()
|
div()
|
||||||
// .with_style(theme.message.container)
|
.id("cancel")
|
||||||
// .aligned()
|
.child(IconElement::new(Icon::Close))
|
||||||
// .top()
|
.cursor_pointer()
|
||||||
// .left()
|
.on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
|
||||||
// .flex(1., true),
|
),
|
||||||
// )
|
)
|
||||||
// .with_child(
|
.child(
|
||||||
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
div()
|
||||||
// let style = theme.dismiss_button.style_for(state);
|
.id("notes")
|
||||||
// Svg::new("icons/x.svg")
|
.child(Label::new("View the release notes"))
|
||||||
// .with_color(style.color)
|
.cursor_pointer()
|
||||||
// .constrained()
|
.on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
|
||||||
// .with_width(style.icon_width)
|
)
|
||||||
// .aligned()
|
|
||||||
// .contained()
|
|
||||||
// .with_style(style.container)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(style.button_width)
|
|
||||||
// .with_height(style.button_width)
|
|
||||||
// })
|
|
||||||
// .with_padding(Padding::uniform(5.))
|
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
// this.dismiss(&Default::default(), cx)
|
|
||||||
// })
|
|
||||||
// .aligned()
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(cx.font_cache().line_height(theme.message.text.font_size))
|
|
||||||
// .aligned()
|
|
||||||
// .top()
|
|
||||||
// .flex_float(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// .with_child({
|
|
||||||
// let style = theme.action_message.style_for(state);
|
|
||||||
// Text::new("View the release notes", style.text.clone())
|
|
||||||
// .contained()
|
|
||||||
// .with_style(style.container)
|
|
||||||
// })
|
|
||||||
// .contained()
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .on_click(MouseButton::Left, |_, _, cx| {
|
|
||||||
// crate::view_release_notes(&Default::default(), cx)
|
|
||||||
// })
|
|
||||||
// .into_any_named("update notification")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateNotification {
|
impl UpdateNotification {
|
||||||
pub fn new(version: SemanticVersion) -> Self {
|
pub fn new(version: SemanticVersion) -> Self {
|
||||||
Self { _version: version }
|
Self { version }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(DismissEvent::Dismiss);
|
cx.emit(DismissEvent::Dismiss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -660,9 +660,12 @@ impl CallHandler for Call {
|
||||||
self.active_call.as_ref().map(|call| {
|
self.active_call.as_ref().map(|call| {
|
||||||
call.0.update(cx, |this, cx| {
|
call.0.update(cx, |this, cx| {
|
||||||
this.room().map(|room| {
|
this.room().map(|room| {
|
||||||
room.update(cx, |this, cx| {
|
let room = room.clone();
|
||||||
this.toggle_mute(cx).log_err();
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::participant::{LocalParticipant, ParticipantLocation, RemoteParticipant};
|
use crate::{
|
||||||
|
call_settings::CallSettings,
|
||||||
|
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use audio::{Audio, Sound};
|
use audio::{Audio, Sound};
|
||||||
use client::{
|
use client::{
|
||||||
|
@ -18,6 +21,7 @@ use live_kit_client::{
|
||||||
};
|
};
|
||||||
use postage::{sink::Sink, stream::Stream, watch};
|
use postage::{sink::Sink, stream::Stream, watch};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use settings::Settings as _;
|
||||||
use std::{future::Future, mem, sync::Arc, time::Duration};
|
use std::{future::Future, mem, sync::Arc, time::Duration};
|
||||||
use util::{post_inc, ResultExt, TryFutureExt};
|
use util::{post_inc, ResultExt, TryFutureExt};
|
||||||
|
|
||||||
|
@ -328,10 +332,8 @@ impl Room {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mute_on_join(_cx: &AppContext) -> bool {
|
pub fn mute_on_join(cx: &AppContext) -> bool {
|
||||||
// todo!() po: This should be uncommented, though then unmuting does not work
|
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
||||||
false
|
|
||||||
//CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_join_response(
|
fn from_join_response(
|
||||||
|
@ -1265,7 +1267,6 @@ impl Room {
|
||||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
let publication = publish_track.await;
|
let publication = publish_track.await;
|
||||||
this.upgrade()
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
|
|
|
@ -1,37 +1,34 @@
|
||||||
use client::{ContactRequestStatus, User, UserStore};
|
use client::{ContactRequestStatus, User, UserStore};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle,
|
div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
|
||||||
|
FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
|
||||||
|
ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
use picker::{Picker, PickerDelegate};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::TryFutureExt;
|
use theme::ActiveTheme as _;
|
||||||
use workspace::Modal;
|
use ui::{h_stack, v_stack, Label};
|
||||||
|
use util::{ResultExt as _, TryFutureExt};
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
Picker::<ContactFinderDelegate>::init(cx);
|
//Picker::<ContactFinderDelegate>::init(cx);
|
||||||
cx.add_action(ContactFinder::dismiss)
|
//cx.add_action(ContactFinder::dismiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ContactFinder {
|
pub struct ContactFinder {
|
||||||
picker: ViewHandle<Picker<ContactFinderDelegate>>,
|
picker: View<Picker<ContactFinderDelegate>>,
|
||||||
has_focus: bool,
|
has_focus: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactFinder {
|
impl ContactFinder {
|
||||||
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let picker = cx.add_view(|cx| {
|
let delegate = ContactFinderDelegate {
|
||||||
Picker::new(
|
parent: cx.view().downgrade(),
|
||||||
ContactFinderDelegate {
|
user_store,
|
||||||
user_store,
|
potential_contacts: Arc::from([]),
|
||||||
potential_contacts: Arc::from([]),
|
selected_index: 0,
|
||||||
selected_index: 0,
|
};
|
||||||
},
|
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
picker,
|
picker,
|
||||||
|
@ -41,105 +38,72 @@ impl ContactFinder {
|
||||||
|
|
||||||
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
||||||
self.picker.update(cx, |picker, cx| {
|
self.picker.update(cx, |picker, cx| {
|
||||||
picker.set_query(query, cx);
|
// todo!()
|
||||||
|
// picker.set_query(query, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
|
||||||
cx.emit(PickerEvent::Dismiss);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ContactFinder {
|
impl Render for ContactFinder {
|
||||||
type Event = PickerEvent;
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
}
|
fn render_mode_button(text: &'static str) -> AnyElement {
|
||||||
|
Label::new(text).into_any_element()
|
||||||
impl View for ContactFinder {
|
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"ContactFinder"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
|
||||||
let full_theme = &theme::current(cx);
|
|
||||||
let theme = &full_theme.collab_panel.tabbed_modal;
|
|
||||||
|
|
||||||
fn render_mode_button(
|
|
||||||
text: &'static str,
|
|
||||||
theme: &theme::TabbedModal,
|
|
||||||
_cx: &mut ViewContext<ContactFinder>,
|
|
||||||
) -> AnyElement<ContactFinder> {
|
|
||||||
let contained_text = &theme.tab_button.active_state().default;
|
|
||||||
Label::new(text, contained_text.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(contained_text.container.clone())
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Flex::column()
|
v_stack()
|
||||||
.with_child(
|
.child(
|
||||||
Flex::column()
|
v_stack()
|
||||||
.with_child(
|
.child(Label::new("Contacts"))
|
||||||
Label::new("Contacts", theme.title.text.clone())
|
.child(h_stack().children([render_mode_button("Invite new contacts")]))
|
||||||
.contained()
|
.bg(cx.theme().colors().element_background),
|
||||||
.with_style(theme.title.container.clone()),
|
|
||||||
)
|
|
||||||
.with_child(Flex::row().with_children([render_mode_button(
|
|
||||||
"Invite new contacts",
|
|
||||||
&theme,
|
|
||||||
cx,
|
|
||||||
)]))
|
|
||||||
.expanded()
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.header),
|
|
||||||
)
|
)
|
||||||
.with_child(
|
.child(self.picker.clone())
|
||||||
ChildView::new(&self.picker, cx)
|
.w_96()
|
||||||
.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>) {
|
// fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
self.has_focus = true;
|
// self.has_focus = true;
|
||||||
if cx.is_self_focused() {
|
// if cx.is_self_focused() {
|
||||||
cx.focus(&self.picker)
|
// cx.focus(&self.picker)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
||||||
self.has_focus = false;
|
// self.has_focus = false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
type Element = Div;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modal for ContactFinder {
|
// impl Modal for ContactFinder {
|
||||||
fn has_focus(&self) -> bool {
|
// fn has_focus(&self) -> bool {
|
||||||
self.has_focus
|
// self.has_focus
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn dismiss_on_event(event: &Self::Event) -> bool {
|
// fn dismiss_on_event(event: &Self::Event) -> bool {
|
||||||
match event {
|
// match event {
|
||||||
PickerEvent::Dismiss => true,
|
// PickerEvent::Dismiss => true,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub struct ContactFinderDelegate {
|
pub struct ContactFinderDelegate {
|
||||||
|
parent: WeakView<ContactFinder>,
|
||||||
potential_contacts: Arc<[Arc<User>]>,
|
potential_contacts: Arc<[Arc<User>]>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for ContactFinderDelegate {
|
impl EventEmitter<DismissEvent> for ContactFinder {}
|
||||||
fn placeholder_text(&self) -> Arc<str> {
|
|
||||||
"Search collaborator by username...".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl FocusableView for ContactFinder {
|
||||||
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
|
self.picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickerDelegate for ContactFinderDelegate {
|
||||||
|
type ListItem = Div;
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
self.potential_contacts.len()
|
self.potential_contacts.len()
|
||||||
}
|
}
|
||||||
|
@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||||
self.selected_index = ix;
|
self.selected_index = ix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
|
"Search collaborator by username...".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
let search_users = self
|
let search_users = self
|
||||||
.user_store
|
.user_store
|
||||||
|
@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||||
async {
|
async {
|
||||||
let potential_contacts = search_users.await?;
|
let potential_contacts = search_users.await?;
|
||||||
picker.update(&mut cx, |picker, cx| {
|
picker.update(&mut cx, |picker, cx| {
|
||||||
picker.delegate_mut().potential_contacts = potential_contacts.into();
|
picker.delegate.potential_contacts = potential_contacts.into();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
cx.emit(PickerEvent::Dismiss);
|
//cx.emit(PickerEvent::Dismiss);
|
||||||
|
self.parent
|
||||||
|
.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
|
||||||
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
mouse_state: &mut MouseState,
|
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &gpui::AppContext,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement<Picker<Self>> {
|
) -> Option<Self::ListItem> {
|
||||||
let full_theme = &theme::current(cx);
|
|
||||||
let theme = &full_theme.collab_panel.contact_finder;
|
|
||||||
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
|
|
||||||
let user = &self.potential_contacts[ix];
|
let user = &self.potential_contacts[ix];
|
||||||
let request_status = self.user_store.read(cx).contact_request_status(user);
|
let request_status = self.user_store.read(cx).contact_request_status(user);
|
||||||
|
|
||||||
|
@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||||
ContactRequestStatus::RequestSent => Some("icons/x.svg"),
|
ContactRequestStatus::RequestSent => Some("icons/x.svg"),
|
||||||
ContactRequestStatus::RequestAccepted => None,
|
ContactRequestStatus::RequestAccepted => None,
|
||||||
};
|
};
|
||||||
let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
|
dbg!(icon_path);
|
||||||
&theme.disabled_contact_button
|
Some(
|
||||||
} else {
|
div()
|
||||||
&theme.contact_button
|
.flex_1()
|
||||||
};
|
.justify_between()
|
||||||
let style = tabbed_modal
|
.children(user.avatar.clone().map(|avatar| img().data(avatar)))
|
||||||
.picker
|
.child(Label::new(user.github_login.clone()))
|
||||||
.item
|
.children(icon_path.map(|icon_path| svg().path(icon_path))),
|
||||||
.in_state(selected)
|
)
|
||||||
.style_for(mouse_state);
|
// Flex::row()
|
||||||
Flex::row()
|
// .with_children(user.avatar.clone().map(|avatar| {
|
||||||
.with_children(user.avatar.clone().map(|avatar| {
|
// Image::from_data(avatar)
|
||||||
Image::from_data(avatar)
|
// .with_style(theme.contact_avatar)
|
||||||
.with_style(theme.contact_avatar)
|
// .aligned()
|
||||||
.aligned()
|
// .left()
|
||||||
.left()
|
// }))
|
||||||
}))
|
// .with_child(
|
||||||
.with_child(
|
// Label::new(user.github_login.clone(), style.label.clone())
|
||||||
Label::new(user.github_login.clone(), style.label.clone())
|
// .contained()
|
||||||
.contained()
|
// .with_style(theme.contact_username)
|
||||||
.with_style(theme.contact_username)
|
// .aligned()
|
||||||
.aligned()
|
// .left(),
|
||||||
.left(),
|
// )
|
||||||
)
|
// .with_children(icon_path.map(|icon_path| {
|
||||||
.with_children(icon_path.map(|icon_path| {
|
// Svg::new(icon_path)
|
||||||
Svg::new(icon_path)
|
// .with_color(button_style.color)
|
||||||
.with_color(button_style.color)
|
// .constrained()
|
||||||
.constrained()
|
// .with_width(button_style.icon_width)
|
||||||
.with_width(button_style.icon_width)
|
// .aligned()
|
||||||
.aligned()
|
// .contained()
|
||||||
.contained()
|
// .with_style(button_style.container)
|
||||||
.with_style(button_style.container)
|
// .constrained()
|
||||||
.constrained()
|
// .with_width(button_style.button_width)
|
||||||
.with_width(button_style.button_width)
|
// .with_height(button_style.button_width)
|
||||||
.with_height(button_style.button_width)
|
// .aligned()
|
||||||
.aligned()
|
// .flex_float()
|
||||||
.flex_float()
|
// }))
|
||||||
}))
|
// .contained()
|
||||||
.contained()
|
// .with_style(style.container)
|
||||||
.with_style(style.container)
|
// .constrained()
|
||||||
.constrained()
|
// .with_height(tabbed_modal.row_height)
|
||||||
.with_height(tabbed_modal.row_height)
|
// .into_any()
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ use project::Project;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
|
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
use crate::face_pile::FacePile;
|
use crate::face_pile::FacePile;
|
||||||
|
|
||||||
|
@ -290,11 +290,13 @@ impl Render for CollabTitlebarItem {
|
||||||
} else {
|
} else {
|
||||||
this.child(Button::new("Sign in").on_click(move |_, cx| {
|
this.child(Button::new("Sign in").on_click(move |_, cx| {
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
cx.spawn(move |cx| async move {
|
cx.spawn(move |mut cx| async move {
|
||||||
client.authenticate_and_connect(true, &cx).await?;
|
client
|
||||||
Ok::<(), anyhow::Error>(())
|
.authenticate_and_connect(true, &cx)
|
||||||
|
.await
|
||||||
|
.notify_async_err(&mut cx);
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach();
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,8 +3,8 @@ use gpui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct FacePile {
|
pub struct FacePile {
|
||||||
faces: Vec<AnyElement>,
|
pub faces: Vec<AnyElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for FacePile {
|
impl RenderOnce for FacePile {
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use collections::{CommandPaletteFilter, HashMap};
|
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
|
||||||
use gpui::{
|
|
||||||
actions, div, prelude::*, Action, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
|
|
||||||
FocusHandle, FocusableView, Keystroke, ParentElement, Render, Styled, View, ViewContext,
|
|
||||||
VisualContext, WeakView,
|
|
||||||
};
|
|
||||||
use picker::{simple_picker_match, Picker, PickerDelegate};
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{self, Reverse},
|
cmp::{self, Reverse},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding};
|
use collections::{CommandPaletteFilter, HashMap};
|
||||||
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
|
use gpui::{
|
||||||
|
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||||
|
Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||||
|
};
|
||||||
|
use picker::{Picker, PickerDelegate};
|
||||||
|
|
||||||
|
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
|
||||||
use util::{
|
use util::{
|
||||||
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
||||||
ResultExt,
|
ResultExt,
|
||||||
|
@ -81,7 +81,7 @@ impl Render for CommandPalette {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
v_stack().w_96().child(self.picker.clone())
|
v_stack().min_w_96().child(self.picker.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +141,8 @@ impl CommandPaletteDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for CommandPaletteDelegate {
|
impl PickerDelegate for CommandPaletteDelegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
fn placeholder_text(&self) -> Arc<str> {
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
"Execute a command...".into()
|
"Execute a command...".into()
|
||||||
}
|
}
|
||||||
|
@ -292,24 +294,26 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement {
|
) -> Option<Self::ListItem> {
|
||||||
let Some(r#match) = self.matches.get(ix) else {
|
let Some(r#match) = self.matches.get(ix) else {
|
||||||
return div().into_any();
|
return None;
|
||||||
};
|
};
|
||||||
let Some(command) = self.commands.get(r#match.candidate_id) else {
|
let Some(command) = self.commands.get(r#match.candidate_id) else {
|
||||||
return div().into_any();
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
simple_picker_match(selected, cx, |cx| {
|
Some(
|
||||||
h_stack()
|
ListItem::new(ix).inset(true).selected(selected).child(
|
||||||
.justify_between()
|
h_stack()
|
||||||
.child(HighlightedLabel::new(
|
.w_full()
|
||||||
command.name.clone(),
|
.justify_between()
|
||||||
r#match.positions.clone(),
|
.child(HighlightedLabel::new(
|
||||||
))
|
command.name.clone(),
|
||||||
.children(KeyBinding::for_action(&*command.action, cx))
|
r#match.positions.clone(),
|
||||||
.into_any()
|
))
|
||||||
})
|
.children(KeyBinding::for_action(&*command.action, cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1273,6 +1273,13 @@ impl CompletionsMenu {
|
||||||
multiline_docs.map(|div| {
|
multiline_docs.map(|div| {
|
||||||
div.id("multiline_docs")
|
div.id("multiline_docs")
|
||||||
.max_h(max_height)
|
.max_h(max_height)
|
||||||
|
.flex_1()
|
||||||
|
.px_1p5()
|
||||||
|
.py_1()
|
||||||
|
.min_w(px(260.))
|
||||||
|
.max_w(px(640.))
|
||||||
|
.w(px(500.))
|
||||||
|
.text_ui()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
// Prevent a mouse down on documentation from being propagated to the editor,
|
// Prevent a mouse down on documentation from being propagated to the editor,
|
||||||
// because that would move the cursor.
|
// because that would move the cursor.
|
||||||
|
@ -1327,13 +1334,18 @@ impl CompletionsMenu {
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(mat.candidate_id)
|
.id(mat.candidate_id)
|
||||||
.min_w(px(300.))
|
.min_w(px(220.))
|
||||||
.max_w(px(700.))
|
.max_w(px(540.))
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.bg(gpui::green())
|
.text_ui()
|
||||||
.hover(|style| style.bg(gpui::blue()))
|
.px_1()
|
||||||
.when(item_ix == selected_item, |div| div.bg(gpui::red()))
|
.rounded(px(4.))
|
||||||
|
.bg(cx.theme().colors().ghost_element_background)
|
||||||
|
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||||
|
.when(item_ix == selected_item, |div| {
|
||||||
|
div.bg(cx.theme().colors().ghost_element_selected)
|
||||||
|
})
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
cx.listener(move |editor, event, cx| {
|
cx.listener(move |editor, event, cx| {
|
||||||
|
|
|
@ -22,10 +22,11 @@ use collections::{BTreeMap, HashMap};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
|
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
|
||||||
BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
|
BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
|
||||||
ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout,
|
ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
|
||||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
|
IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
|
ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||||
TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
|
StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
|
||||||
|
ViewContext, WeakView, WindowContext, WrappedLine,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::language_settings::ShowWhitespaceSetting;
|
use language::language_settings::ShowWhitespaceSetting;
|
||||||
|
@ -316,6 +317,7 @@ impl EditorElement {
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut click_count = event.click_count;
|
let mut click_count = event.click_count;
|
||||||
|
@ -326,6 +328,9 @@ impl EditorElement {
|
||||||
} else if !text_bounds.contains_point(&event.position) {
|
} else if !text_bounds.contains_point(&event.position) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if !cx.was_top_layer(&event.position, stacking_order) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||||
let position = point_for_position.previous_valid;
|
let position = point_for_position.previous_valid;
|
||||||
|
@ -384,6 +389,7 @@ impl EditorElement {
|
||||||
event: &MouseUpEvent,
|
event: &MouseUpEvent,
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let end_selection = editor.has_pending_selection();
|
let end_selection = editor.has_pending_selection();
|
||||||
|
@ -396,6 +402,7 @@ impl EditorElement {
|
||||||
if !pending_nonempty_selections
|
if !pending_nonempty_selections
|
||||||
&& event.modifiers.command
|
&& event.modifiers.command
|
||||||
&& text_bounds.contains_point(&event.position)
|
&& text_bounds.contains_point(&event.position)
|
||||||
|
&& cx.was_top_layer(&event.position, stacking_order)
|
||||||
{
|
{
|
||||||
let point = position_map.point_for_position(text_bounds, event.position);
|
let point = position_map.point_for_position(text_bounds, event.position);
|
||||||
let could_be_inlay = point.as_valid().is_none();
|
let could_be_inlay = point.as_valid().is_none();
|
||||||
|
@ -418,6 +425,7 @@ impl EditorElement {
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let modifiers = event.modifiers;
|
let modifiers = event.modifiers;
|
||||||
|
@ -457,10 +465,12 @@ impl EditorElement {
|
||||||
|
|
||||||
let text_hovered = text_bounds.contains_point(&event.position);
|
let text_hovered = text_bounds.contains_point(&event.position);
|
||||||
let gutter_hovered = gutter_bounds.contains_point(&event.position);
|
let gutter_hovered = gutter_bounds.contains_point(&event.position);
|
||||||
|
let was_top = cx.was_top_layer(&event.position, stacking_order);
|
||||||
|
|
||||||
editor.set_gutter_hovered(gutter_hovered, cx);
|
editor.set_gutter_hovered(gutter_hovered, cx);
|
||||||
|
|
||||||
// Don't trigger hover popover if mouse is hovering over context menu
|
// Don't trigger hover popover if mouse is hovering over context menu
|
||||||
if text_hovered {
|
if text_hovered && was_top {
|
||||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||||
|
|
||||||
match point_for_position.as_valid() {
|
match point_for_position.as_valid() {
|
||||||
|
@ -490,7 +500,7 @@ impl EditorElement {
|
||||||
} else {
|
} else {
|
||||||
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
||||||
hover_at(editor, None, cx);
|
hover_at(editor, None, cx);
|
||||||
gutter_hovered
|
gutter_hovered && was_top
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,10 +508,10 @@ impl EditorElement {
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
event: &ScrollWheelEvent,
|
event: &ScrollWheelEvent,
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: &InteractiveBounds,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if !bounds.contains_point(&event.position) {
|
if !bounds.visibly_contains(&event.position, cx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2282,10 +2292,15 @@ impl EditorElement {
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||||
|
let interactive_bounds = InteractiveBounds {
|
||||||
|
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||||
|
stacking_order: cx.stacking_order().clone(),
|
||||||
|
};
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
move |event: &ScrollWheelEvent, phase, cx| {
|
move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase != DispatchPhase::Bubble {
|
||||||
|
@ -2293,7 +2308,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_cancel = editor.update(cx, |editor, cx| {
|
let should_cancel = editor.update(cx, |editor, cx| {
|
||||||
Self::scroll(editor, event, &position_map, bounds, cx)
|
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
||||||
});
|
});
|
||||||
if should_cancel {
|
if should_cancel {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
|
@ -2304,6 +2319,7 @@ impl EditorElement {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
|
||||||
move |event: &MouseDownEvent, phase, cx| {
|
move |event: &MouseDownEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase != DispatchPhase::Bubble {
|
||||||
|
@ -2311,7 +2327,15 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_cancel = editor.update(cx, |editor, cx| {
|
let should_cancel = editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx)
|
Self::mouse_down(
|
||||||
|
editor,
|
||||||
|
event,
|
||||||
|
&position_map,
|
||||||
|
text_bounds,
|
||||||
|
gutter_bounds,
|
||||||
|
&stacking_order,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if should_cancel {
|
if should_cancel {
|
||||||
|
@ -2323,9 +2347,18 @@ impl EditorElement {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
|
||||||
move |event: &MouseUpEvent, phase, cx| {
|
move |event: &MouseUpEvent, phase, cx| {
|
||||||
let should_cancel = editor.update(cx, |editor, cx| {
|
let should_cancel = editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_up(editor, event, &position_map, text_bounds, cx)
|
Self::mouse_up(
|
||||||
|
editor,
|
||||||
|
event,
|
||||||
|
&position_map,
|
||||||
|
text_bounds,
|
||||||
|
&stacking_order,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if should_cancel {
|
if should_cancel {
|
||||||
|
@ -2351,13 +2384,23 @@ impl EditorElement {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
|
||||||
move |event: &MouseMoveEvent, phase, cx| {
|
move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase != DispatchPhase::Bubble {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop_propogating = editor.update(cx, |editor, cx| {
|
let stop_propogating = editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx)
|
Self::mouse_moved(
|
||||||
|
editor,
|
||||||
|
event,
|
||||||
|
&position_map,
|
||||||
|
text_bounds,
|
||||||
|
gutter_bounds,
|
||||||
|
&stacking_order,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if stop_propogating {
|
if stop_propogating {
|
||||||
|
@ -2617,9 +2660,11 @@ impl Element for EditorElement {
|
||||||
// We call with_z_index to establish a new stacking context.
|
// We call with_z_index to establish a new stacking context.
|
||||||
cx.with_z_index(0, |cx| {
|
cx.with_z_index(0, |cx| {
|
||||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
// Paint mouse listeners first, so any elements we paint on top of the editor
|
// Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
|
||||||
// take precedence.
|
// take precedence.
|
||||||
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
|
cx.with_z_index(0, |cx| {
|
||||||
|
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
|
||||||
|
});
|
||||||
let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
||||||
cx.handle_input(&focus_handle, input_handler);
|
cx.handle_input(&focus_handle, input_handler);
|
||||||
|
|
||||||
|
|
|
@ -483,9 +483,6 @@ impl InfoPopover {
|
||||||
// Prevent a mouse move on the popover from being propagated to the editor,
|
// Prevent a mouse move on the popover from being propagated to the editor,
|
||||||
// because that would dismiss the popover.
|
// because that would dismiss the popover.
|
||||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||||
// Prevent a mouse down on the popover from being propagated to the editor,
|
|
||||||
// because that would move the cursor.
|
|
||||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
|
||||||
.child(crate::render_parsed_markdown(
|
.child(crate::render_parsed_markdown(
|
||||||
"content",
|
"content",
|
||||||
&self.parsed_content,
|
&self.parsed_content,
|
||||||
|
|
|
@ -2,9 +2,8 @@ use collections::HashMap;
|
||||||
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
|
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
|
||||||
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
|
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, AnyElement, AppContext, DismissEvent, Div, Element, EventEmitter, FocusHandle,
|
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
|
||||||
FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task,
|
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||||
View, ViewContext, VisualContext, WeakView,
|
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||||
|
@ -16,8 +15,7 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use theme::ActiveTheme;
|
use ui::{v_stack, HighlightedLabel, ListItem};
|
||||||
use ui::{v_stack, HighlightedLabel, StyledExt};
|
|
||||||
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -530,6 +528,8 @@ impl FileFinderDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for FileFinderDelegate {
|
impl PickerDelegate for FileFinderDelegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
fn placeholder_text(&self) -> Arc<str> {
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
"Search project files...".into()
|
"Search project files...".into()
|
||||||
}
|
}
|
||||||
|
@ -709,31 +709,22 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement {
|
) -> Option<Self::ListItem> {
|
||||||
let path_match = self
|
let path_match = self
|
||||||
.matches
|
.matches
|
||||||
.get(ix)
|
.get(ix)
|
||||||
.expect("Invalid matches state: no element for index {ix}");
|
.expect("Invalid matches state: no element for index {ix}");
|
||||||
let theme = cx.theme();
|
|
||||||
let colors = theme.colors();
|
|
||||||
|
|
||||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||||
self.labels_for_match(path_match, cx, ix);
|
self.labels_for_match(path_match, cx, ix);
|
||||||
|
|
||||||
div()
|
Some(
|
||||||
.px_1()
|
ListItem::new(ix).inset(true).selected(selected).child(
|
||||||
.text_color(colors.text)
|
|
||||||
.text_ui()
|
|
||||||
.bg(colors.ghost_element_background)
|
|
||||||
.rounded_md()
|
|
||||||
.when(selected, |this| this.bg(colors.ghost_element_selected))
|
|
||||||
.hover(|this| this.bg(colors.ghost_element_hover))
|
|
||||||
.child(
|
|
||||||
v_stack()
|
v_stack()
|
||||||
.child(HighlightedLabel::new(file_name, file_name_positions))
|
.child(HighlightedLabel::new(file_name, file_name_positions))
|
||||||
.child(HighlightedLabel::new(full_path, full_path_positions)),
|
.child(HighlightedLabel::new(full_path, full_path_positions)),
|
||||||
)
|
),
|
||||||
.into_any()
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ use crate::{
|
||||||
BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
|
BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
|
||||||
IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
|
IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
|
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
|
||||||
SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext,
|
SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
@ -84,7 +85,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
move |event, bounds, phase, cx| {
|
move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble
|
||||||
&& event.button == button
|
&& event.button == button
|
||||||
&& bounds.contains_point(&event.position)
|
&& bounds.visibly_contains(&event.position, cx)
|
||||||
{
|
{
|
||||||
(listener)(event, cx)
|
(listener)(event, cx)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +100,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.interactivity().mouse_down_listeners.push(Box::new(
|
self.interactivity().mouse_down_listeners.push(Box::new(
|
||||||
move |event, bounds, phase, cx| {
|
move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||||
(listener)(event, cx)
|
(listener)(event, cx)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -117,7 +118,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
.push(Box::new(move |event, bounds, phase, cx| {
|
.push(Box::new(move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble
|
||||||
&& event.button == button
|
&& event.button == button
|
||||||
&& bounds.contains_point(&event.position)
|
&& bounds.visibly_contains(&event.position, cx)
|
||||||
{
|
{
|
||||||
(listener)(event, cx)
|
(listener)(event, cx)
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
self.interactivity()
|
self.interactivity()
|
||||||
.mouse_up_listeners
|
.mouse_up_listeners
|
||||||
.push(Box::new(move |event, bounds, phase, cx| {
|
.push(Box::new(move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||||
(listener)(event, cx)
|
(listener)(event, cx)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -145,7 +146,8 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.interactivity().mouse_down_listeners.push(Box::new(
|
self.interactivity().mouse_down_listeners.push(Box::new(
|
||||||
move |event, bounds, phase, cx| {
|
move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
(listener)(event, cx)
|
(listener)(event, cx)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -163,7 +165,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
.push(Box::new(move |event, bounds, phase, cx| {
|
.push(Box::new(move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture
|
if phase == DispatchPhase::Capture
|
||||||
&& event.button == button
|
&& event.button == button
|
||||||
&& !bounds.contains_point(&event.position)
|
&& !bounds.visibly_contains(&event.position, cx)
|
||||||
{
|
{
|
||||||
(listener)(event, cx);
|
(listener)(event, cx);
|
||||||
}
|
}
|
||||||
|
@ -177,7 +179,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.interactivity().mouse_move_listeners.push(Box::new(
|
self.interactivity().mouse_move_listeners.push(Box::new(
|
||||||
move |event, bounds, phase, cx| {
|
move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||||
(listener)(event, cx);
|
(listener)(event, cx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -191,7 +193,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.interactivity().scroll_wheel_listeners.push(Box::new(
|
self.interactivity().scroll_wheel_listeners.push(Box::new(
|
||||||
move |event, bounds, phase, cx| {
|
move |event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||||
(listener)(event, cx);
|
(listener)(event, cx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -526,15 +528,15 @@ pub type FocusListeners = SmallVec<[FocusListener; 2]>;
|
||||||
pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
|
pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
pub type MouseDownListener =
|
pub type MouseDownListener =
|
||||||
Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||||
pub type MouseUpListener =
|
pub type MouseUpListener =
|
||||||
Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
pub type MouseMoveListener =
|
pub type MouseMoveListener =
|
||||||
Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
pub type ScrollWheelListener =
|
pub type ScrollWheelListener =
|
||||||
Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
|
@ -719,6 +721,18 @@ pub struct Interactivity {
|
||||||
pub tooltip_builder: Option<TooltipBuilder>,
|
pub tooltip_builder: Option<TooltipBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct InteractiveBounds {
|
||||||
|
pub bounds: Bounds<Pixels>,
|
||||||
|
pub stacking_order: StackingOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteractiveBounds {
|
||||||
|
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||||
|
self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Interactivity {
|
impl Interactivity {
|
||||||
pub fn layout(
|
pub fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -755,34 +769,52 @@ impl Interactivity {
|
||||||
) {
|
) {
|
||||||
let style = self.compute_style(Some(bounds), element_state, cx);
|
let style = self.compute_style(Some(bounds), element_state, cx);
|
||||||
|
|
||||||
|
if style
|
||||||
|
.background
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
|
||||||
|
{
|
||||||
|
cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
|
||||||
|
}
|
||||||
|
|
||||||
|
let interactive_bounds = Rc::new(InteractiveBounds {
|
||||||
|
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||||
|
stacking_order: cx.stacking_order().clone(),
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(mouse_cursor) = style.mouse_cursor {
|
if let Some(mouse_cursor) = style.mouse_cursor {
|
||||||
let hovered = bounds.contains_point(&cx.mouse_position());
|
let mouse_position = &cx.mouse_position();
|
||||||
|
let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
|
||||||
if hovered {
|
if hovered {
|
||||||
cx.set_cursor_style(mouse_cursor);
|
cx.set_cursor_style(mouse_cursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for listener in self.mouse_down_listeners.drain(..) {
|
for listener in self.mouse_down_listeners.drain(..) {
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||||
listener(event, &bounds, phase, cx);
|
listener(event, &*interactive_bounds, phase, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for listener in self.mouse_up_listeners.drain(..) {
|
for listener in self.mouse_up_listeners.drain(..) {
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||||
listener(event, &bounds, phase, cx);
|
listener(event, &*interactive_bounds, phase, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for listener in self.mouse_move_listeners.drain(..) {
|
for listener in self.mouse_move_listeners.drain(..) {
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
listener(event, &bounds, phase, cx);
|
listener(event, &*interactive_bounds, phase, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for listener in self.scroll_wheel_listeners.drain(..) {
|
for listener in self.scroll_wheel_listeners.drain(..) {
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
listener(event, &bounds, phase, cx);
|
listener(event, &*interactive_bounds, phase, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,6 +824,7 @@ impl Interactivity {
|
||||||
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
||||||
|
|
||||||
if let Some(group_bounds) = hover_group_bounds {
|
if let Some(group_bounds) = hover_group_bounds {
|
||||||
|
// todo!() needs cx.was_top_layer
|
||||||
let hovered = group_bounds.contains_point(&cx.mouse_position());
|
let hovered = group_bounds.contains_point(&cx.mouse_position());
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
|
@ -805,10 +838,11 @@ impl Interactivity {
|
||||||
if self.hover_style.is_some()
|
if self.hover_style.is_some()
|
||||||
|| (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
|
|| (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
|
||||||
{
|
{
|
||||||
let hovered = bounds.contains_point(&cx.mouse_position());
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx);
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
if bounds.contains_point(&event.position) != hovered {
|
if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -817,8 +851,11 @@ impl Interactivity {
|
||||||
|
|
||||||
if cx.active_drag.is_some() {
|
if cx.active_drag.is_some() {
|
||||||
let drop_listeners = mem::take(&mut self.drop_listeners);
|
let drop_listeners = mem::take(&mut self.drop_listeners);
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble
|
||||||
|
&& interactive_bounds.visibly_contains(&event.position, &cx)
|
||||||
|
{
|
||||||
if let Some(drag_state_type) =
|
if let Some(drag_state_type) =
|
||||||
cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
|
cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
|
||||||
{
|
{
|
||||||
|
@ -847,6 +884,7 @@ impl Interactivity {
|
||||||
if let Some(mouse_down) = mouse_down {
|
if let Some(mouse_down) = mouse_down {
|
||||||
if let Some(drag_listener) = drag_listener {
|
if let Some(drag_listener) = drag_listener {
|
||||||
let active_state = element_state.clicked_state.clone();
|
let active_state = element_state.clicked_state.clone();
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if cx.active_drag.is_some() {
|
if cx.active_drag.is_some() {
|
||||||
|
@ -854,7 +892,7 @@ impl Interactivity {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
} else if phase == DispatchPhase::Bubble
|
} else if phase == DispatchPhase::Bubble
|
||||||
&& bounds.contains_point(&event.position)
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
&& (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
|
&& (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
|
||||||
{
|
{
|
||||||
*active_state.borrow_mut() = ElementClickedState::default();
|
*active_state.borrow_mut() = ElementClickedState::default();
|
||||||
|
@ -867,8 +905,11 @@ impl Interactivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble
|
||||||
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
let mouse_click = ClickEvent {
|
let mouse_click = ClickEvent {
|
||||||
down: mouse_down.clone(),
|
down: mouse_down.clone(),
|
||||||
up: event.clone(),
|
up: event.clone(),
|
||||||
|
@ -881,8 +922,11 @@ impl Interactivity {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble
|
||||||
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
*pending_mouse_down.borrow_mut() = Some(event.clone());
|
*pending_mouse_down.borrow_mut() = Some(event.clone());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -893,13 +937,14 @@ impl Interactivity {
|
||||||
if let Some(hover_listener) = self.hover_listener.take() {
|
if let Some(hover_listener) = self.hover_listener.take() {
|
||||||
let was_hovered = element_state.hover_state.clone();
|
let was_hovered = element_state.hover_state.clone();
|
||||||
let has_mouse_down = element_state.pending_mouse_down.clone();
|
let has_mouse_down = element_state.pending_mouse_down.clone();
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase != DispatchPhase::Bubble {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let is_hovered =
|
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none();
|
&& has_mouse_down.borrow().is_none();
|
||||||
let mut was_hovered = was_hovered.borrow_mut();
|
let mut was_hovered = was_hovered.borrow_mut();
|
||||||
|
|
||||||
if is_hovered != was_hovered.clone() {
|
if is_hovered != was_hovered.clone() {
|
||||||
|
@ -914,14 +959,15 @@ impl Interactivity {
|
||||||
if let Some(tooltip_builder) = self.tooltip_builder.take() {
|
if let Some(tooltip_builder) = self.tooltip_builder.take() {
|
||||||
let active_tooltip = element_state.active_tooltip.clone();
|
let active_tooltip = element_state.active_tooltip.clone();
|
||||||
let pending_mouse_down = element_state.pending_mouse_down.clone();
|
let pending_mouse_down = element_state.pending_mouse_down.clone();
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase != DispatchPhase::Bubble {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_hovered =
|
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none();
|
&& pending_mouse_down.borrow().is_none();
|
||||||
if !is_hovered {
|
if !is_hovered {
|
||||||
active_tooltip.borrow_mut().take();
|
active_tooltip.borrow_mut().take();
|
||||||
return;
|
return;
|
||||||
|
@ -979,11 +1025,12 @@ impl Interactivity {
|
||||||
.group_active_style
|
.group_active_style
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|group_active| GroupBounds::get(&group_active.group, cx));
|
.and_then(|group_active| GroupBounds::get(&group_active.group, cx));
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
|
cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
let group = active_group_bounds
|
let group = active_group_bounds
|
||||||
.map_or(false, |bounds| bounds.contains_point(&down.position));
|
.map_or(false, |bounds| bounds.contains_point(&down.position));
|
||||||
let element = bounds.contains_point(&down.position);
|
let element = interactive_bounds.visibly_contains(&down.position, cx);
|
||||||
if group || element {
|
if group || element {
|
||||||
*active_state.borrow_mut() = ElementClickedState { group, element };
|
*active_state.borrow_mut() = ElementClickedState { group, element };
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -1000,9 +1047,12 @@ impl Interactivity {
|
||||||
.clone();
|
.clone();
|
||||||
let line_height = cx.line_height();
|
let line_height = cx.line_height();
|
||||||
let scroll_max = (content_size - bounds.size).max(&Size::default());
|
let scroll_max = (content_size - bounds.size).max(&Size::default());
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if phase == DispatchPhase::Bubble
|
||||||
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
let mut scroll_offset = scroll_offset.borrow_mut();
|
let mut scroll_offset = scroll_offset.borrow_mut();
|
||||||
let old_scroll_offset = *scroll_offset;
|
let old_scroll_offset = *scroll_offset;
|
||||||
let delta = event.delta.pixel_delta(line_height);
|
let delta = event.delta.pixel_delta(line_height);
|
||||||
|
@ -1098,19 +1148,21 @@ impl Interactivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if self.hover_style.is_some() {
|
if self.hover_style.is_some() {
|
||||||
if bounds.contains_point(&mouse_position) {
|
if bounds
|
||||||
// eprintln!("div hovered {bounds:?} {mouse_position:?}");
|
.intersect(&cx.content_mask().bounds)
|
||||||
style.refine(&self.hover_style);
|
.contains_point(&mouse_position)
|
||||||
} else {
|
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||||
// eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
|
{
|
||||||
|
style.refine(&self.hover_style);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if let Some(drag) = cx.active_drag.take() {
|
if let Some(drag) = cx.active_drag.take() {
|
||||||
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
||||||
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
|
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
|
||||||
if *state_type == drag.view.entity_type()
|
if *state_type == drag.view.entity_type()
|
||||||
|
// todo!() needs to handle cx.content_mask() and cx.is_top()
|
||||||
&& group_bounds.contains_point(&mouse_position)
|
&& group_bounds.contains_point(&mouse_position)
|
||||||
{
|
{
|
||||||
style.refine(&group_drag_style.style);
|
style.refine(&group_drag_style.style);
|
||||||
|
@ -1120,7 +1172,10 @@ impl Interactivity {
|
||||||
|
|
||||||
for (state_type, drag_over_style) in &self.drag_over_styles {
|
for (state_type, drag_over_style) in &self.drag_over_styles {
|
||||||
if *state_type == drag.view.entity_type()
|
if *state_type == drag.view.entity_type()
|
||||||
&& bounds.contains_point(&mouse_position)
|
&& bounds
|
||||||
|
.intersect(&cx.content_mask().bounds)
|
||||||
|
.contains_point(&mouse_position)
|
||||||
|
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||||
{
|
{
|
||||||
style.refine(drag_over_style);
|
style.refine(drag_over_style);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,8 @@ use util::ResultExt;
|
||||||
|
|
||||||
/// A global stacking order, which is created by stacking successive z-index values.
|
/// A global stacking order, which is created by stacking successive z-index values.
|
||||||
/// Each z-index will always be interpreted in the context of its parent z-index.
|
/// Each z-index will always be interpreted in the context of its parent z-index.
|
||||||
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)]
|
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]
|
||||||
pub(crate) struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
|
pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
|
||||||
|
|
||||||
/// Represents the two different phases when dispatching events.
|
/// Represents the two different phases when dispatching events.
|
||||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -243,7 +243,8 @@ pub(crate) struct Frame {
|
||||||
pub(crate) dispatch_tree: DispatchTree,
|
pub(crate) dispatch_tree: DispatchTree,
|
||||||
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
||||||
pub(crate) scene_builder: SceneBuilder,
|
pub(crate) scene_builder: SceneBuilder,
|
||||||
z_index_stack: StackingOrder,
|
pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
|
||||||
|
pub(crate) z_index_stack: StackingOrder,
|
||||||
content_mask_stack: Vec<ContentMask<Pixels>>,
|
content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||||
element_offset_stack: Vec<Point<Pixels>>,
|
element_offset_stack: Vec<Point<Pixels>>,
|
||||||
}
|
}
|
||||||
|
@ -257,6 +258,7 @@ impl Frame {
|
||||||
focus_listeners: Vec::new(),
|
focus_listeners: Vec::new(),
|
||||||
scene_builder: SceneBuilder::default(),
|
scene_builder: SceneBuilder::default(),
|
||||||
z_index_stack: StackingOrder::default(),
|
z_index_stack: StackingOrder::default(),
|
||||||
|
depth_map: Default::default(),
|
||||||
content_mask_stack: Vec::new(),
|
content_mask_stack: Vec::new(),
|
||||||
element_offset_stack: Vec::new(),
|
element_offset_stack: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -806,6 +808,32 @@ impl<'a> WindowContext<'a> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called during painting to track which z-index is on top at each pixel position
|
||||||
|
pub fn add_opaque_layer(&mut self, bounds: Bounds<Pixels>) {
|
||||||
|
let stacking_order = self.window.current_frame.z_index_stack.clone();
|
||||||
|
let depth_map = &mut self.window.current_frame.depth_map;
|
||||||
|
match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(&level)) {
|
||||||
|
Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the top-most opaque layer painted over this point was part of the
|
||||||
|
/// same layer as the given stacking order.
|
||||||
|
pub fn was_top_layer(&self, point: &Point<Pixels>, level: &StackingOrder) -> bool {
|
||||||
|
for (stack, bounds) in self.window.previous_frame.depth_map.iter() {
|
||||||
|
if bounds.contains_point(point) {
|
||||||
|
return level.starts_with(stack) || stack.starts_with(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called during painting to get the current stacking order.
|
||||||
|
pub fn stacking_order(&self) -> &StackingOrder {
|
||||||
|
&self.window.current_frame.z_index_stack
|
||||||
|
}
|
||||||
|
|
||||||
/// Paint one or more drop shadows into the scene for the current frame at the current z-index.
|
/// Paint one or more drop shadows into the scene for the current frame at the current z-index.
|
||||||
pub fn paint_shadows(
|
pub fn paint_shadows(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1153,6 +1181,7 @@ impl<'a> WindowContext<'a> {
|
||||||
frame.mouse_listeners.values_mut().for_each(Vec::clear);
|
frame.mouse_listeners.values_mut().for_each(Vec::clear);
|
||||||
frame.focus_listeners.clear();
|
frame.focus_listeners.clear();
|
||||||
frame.dispatch_tree.clear();
|
frame.dispatch_tree.clear();
|
||||||
|
frame.depth_map.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch a mouse or keyboard event on the window.
|
/// Dispatch a mouse or keyboard event on the window.
|
||||||
|
|
|
@ -73,6 +73,7 @@ impl RealNodeRuntime {
|
||||||
let npm_file = node_dir.join("bin/npm");
|
let npm_file = node_dir.join("bin/npm");
|
||||||
|
|
||||||
let result = Command::new(&node_binary)
|
let result = Command::new(&node_binary)
|
||||||
|
.env_clear()
|
||||||
.arg(npm_file)
|
.arg(npm_file)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
|
@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut command = Command::new(node_binary);
|
let mut command = Command::new(node_binary);
|
||||||
|
command.env_clear();
|
||||||
command.env("PATH", env_path);
|
command.env("PATH", env_path);
|
||||||
command.arg(npm_file).arg(subcommand);
|
command.arg(npm_file).arg(subcommand);
|
||||||
command.args(["--cache".into(), installation_path.join("cache")]);
|
command.args(["--cache".into(), installation_path.join("cache")]);
|
||||||
|
@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime {
|
||||||
&[
|
&[
|
||||||
name,
|
name,
|
||||||
"--json",
|
"--json",
|
||||||
"-fetch-retry-mintimeout",
|
"--fetch-retry-mintimeout",
|
||||||
"2000",
|
"2000",
|
||||||
"-fetch-retry-maxtimeout",
|
"--fetch-retry-maxtimeout",
|
||||||
"5000",
|
"5000",
|
||||||
"-fetch-timeout",
|
"--fetch-timeout",
|
||||||
"5000",
|
"5000",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime {
|
||||||
|
|
||||||
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
|
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
|
||||||
arguments.extend_from_slice(&[
|
arguments.extend_from_slice(&[
|
||||||
"-fetch-retry-mintimeout",
|
"--fetch-retry-mintimeout",
|
||||||
"2000",
|
"2000",
|
||||||
"-fetch-retry-maxtimeout",
|
"--fetch-retry-maxtimeout",
|
||||||
"5000",
|
"5000",
|
||||||
"-fetch-timeout",
|
"--fetch-timeout",
|
||||||
"5000",
|
"5000",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub struct Picker<D: PickerDelegate> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PickerDelegate: Sized + 'static {
|
pub trait PickerDelegate: Sized + 'static {
|
||||||
|
type ListItem: IntoElement;
|
||||||
fn match_count(&self) -> usize;
|
fn match_count(&self) -> usize;
|
||||||
fn selected_index(&self) -> usize;
|
fn selected_index(&self) -> usize;
|
||||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
|
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
|
||||||
|
@ -31,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static {
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement;
|
) -> Option<Self::ListItem>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: PickerDelegate> FocusableView for Picker<D> {
|
impl<D: PickerDelegate> FocusableView for Picker<D> {
|
||||||
|
@ -113,7 +114,6 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||||
dbg!("canceling!");
|
|
||||||
self.delegate.dismissed(cx);
|
self.delegate.dismissed(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(picker.delegate.render_match(
|
.children(picker.delegate.render_match(
|
||||||
ix,
|
ix,
|
||||||
ix == selected_index,
|
ix == selected_index,
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -10,9 +10,8 @@ use anyhow::{anyhow, Result};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
|
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
|
||||||
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
|
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
|
||||||
IntoElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
|
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled,
|
||||||
Render, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View,
|
Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||||
ViewContext, VisualContext as _, WeakView, WindowContext,
|
|
||||||
};
|
};
|
||||||
use menu::{Confirm, SelectNext, SelectPrev};
|
use menu::{Confirm, SelectNext, SelectPrev};
|
||||||
use project::{
|
use project::{
|
||||||
|
@ -30,7 +29,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use theme::ActiveTheme as _;
|
use theme::ActiveTheme as _;
|
||||||
use ui::{h_stack, v_stack, IconElement, Label};
|
use ui::{v_stack, IconElement, Label, ListItem};
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -1335,13 +1334,19 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_entry_visual_element(
|
fn render_entry(
|
||||||
details: &EntryDetails,
|
&self,
|
||||||
editor: Option<&View<Editor>>,
|
entry_id: ProjectEntryId,
|
||||||
padding: Pixels,
|
details: EntryDetails,
|
||||||
|
// dragged_entry_destination: &mut Option<Arc<Path>>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Div {
|
) -> ListItem {
|
||||||
|
let kind = details.kind;
|
||||||
|
let settings = ProjectPanelSettings::get_global(cx);
|
||||||
let show_editor = details.is_editing && !details.is_processing;
|
let show_editor = details.is_editing && !details.is_processing;
|
||||||
|
let is_selected = self
|
||||||
|
.selection
|
||||||
|
.map_or(false, |selection| selection.entry_id == entry_id);
|
||||||
|
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let filename_text_color = details
|
let filename_text_color = details
|
||||||
|
@ -1354,14 +1359,17 @@ impl ProjectPanel {
|
||||||
})
|
})
|
||||||
.unwrap_or(theme.status().info);
|
.unwrap_or(theme.status().info);
|
||||||
|
|
||||||
h_stack()
|
ListItem::new(entry_id.to_proto() as usize)
|
||||||
|
.indent_level(details.depth)
|
||||||
|
.indent_step_size(px(settings.indent_size))
|
||||||
|
.selected(is_selected)
|
||||||
.child(if let Some(icon) = &details.icon {
|
.child(if let Some(icon) = &details.icon {
|
||||||
div().child(IconElement::from_path(icon.to_string()))
|
div().child(IconElement::from_path(icon.to_string()))
|
||||||
} else {
|
} else {
|
||||||
div()
|
div()
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
if let (Some(editor), true) = (editor, show_editor) {
|
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
|
||||||
div().w_full().child(editor.clone())
|
div().w_full().child(editor.clone())
|
||||||
} else {
|
} else {
|
||||||
div()
|
div()
|
||||||
|
@ -1370,33 +1378,6 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
.ml_1(),
|
.ml_1(),
|
||||||
)
|
)
|
||||||
.pl(padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_entry(
|
|
||||||
&self,
|
|
||||||
entry_id: ProjectEntryId,
|
|
||||||
details: EntryDetails,
|
|
||||||
// dragged_entry_destination: &mut Option<Arc<Path>>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Stateful<Div> {
|
|
||||||
let kind = details.kind;
|
|
||||||
let settings = ProjectPanelSettings::get_global(cx);
|
|
||||||
const INDENT_SIZE: Pixels = px(16.0);
|
|
||||||
let padding = INDENT_SIZE + details.depth as f32 * px(settings.indent_size);
|
|
||||||
let show_editor = details.is_editing && !details.is_processing;
|
|
||||||
let is_selected = self
|
|
||||||
.selection
|
|
||||||
.map_or(false, |selection| selection.entry_id == entry_id);
|
|
||||||
|
|
||||||
Self::render_entry_visual_element(&details, Some(&self.filename_editor), padding, cx)
|
|
||||||
.id(entry_id.to_proto() as usize)
|
|
||||||
.w_full()
|
|
||||||
.cursor_pointer()
|
|
||||||
.when(is_selected, |this| {
|
|
||||||
this.bg(cx.theme().colors().element_selected)
|
|
||||||
})
|
|
||||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
|
||||||
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
|
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
|
||||||
if !show_editor {
|
if !show_editor {
|
||||||
if kind.is_dir() {
|
if kind.is_dir() {
|
||||||
|
@ -1410,12 +1391,9 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.on_mouse_down(
|
.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
|
||||||
MouseButton::Right,
|
this.deploy_context_menu(event.position, entry_id, cx);
|
||||||
cx.listener(move |this, event: &MouseDownEvent, cx| {
|
}))
|
||||||
this.deploy_context_menu(event.position, entry_id, cx);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
// .on_drop::<ProjectEntryId>(|this, event, cx| {
|
// .on_drop::<ProjectEntryId>(|this, event, cx| {
|
||||||
// this.move_entry(
|
// this.move_entry(
|
||||||
// *dragged_entry,
|
// *dragged_entry,
|
||||||
|
|
|
@ -33,7 +33,6 @@ impl Render for FocusStory {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let color_1 = theme.status().created;
|
let color_1 = theme.status().created;
|
||||||
let color_2 = theme.status().modified;
|
let color_2 = theme.status().modified;
|
||||||
let color_3 = theme.status().deleted;
|
|
||||||
let color_4 = theme.status().conflict;
|
let color_4 = theme.status().conflict;
|
||||||
let color_5 = theme.status().ignored;
|
let color_5 = theme.status().ignored;
|
||||||
let color_6 = theme.status().renamed;
|
let color_6 = theme.status().renamed;
|
||||||
|
@ -42,10 +41,10 @@ impl Render for FocusStory {
|
||||||
.id("parent")
|
.id("parent")
|
||||||
.focusable()
|
.focusable()
|
||||||
.key_context("parent")
|
.key_context("parent")
|
||||||
.on_action(cx.listener(|_, action: &ActionA, cx| {
|
.on_action(cx.listener(|_, _action: &ActionA, _cx| {
|
||||||
println!("Action A dispatched on parent");
|
println!("Action A dispatched on parent");
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|_, action: &ActionB, cx| {
|
.on_action(cx.listener(|_, _action: &ActionB, _cx| {
|
||||||
println!("Action B dispatched on parent");
|
println!("Action B dispatched on parent");
|
||||||
}))
|
}))
|
||||||
.on_focus(cx.listener(|_, _, _| println!("Parent focused")))
|
.on_focus(cx.listener(|_, _, _| println!("Parent focused")))
|
||||||
|
@ -61,7 +60,7 @@ impl Render for FocusStory {
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.child_1_focus)
|
.track_focus(&self.child_1_focus)
|
||||||
.key_context("child-1")
|
.key_context("child-1")
|
||||||
.on_action(cx.listener(|_, action: &ActionB, cx| {
|
.on_action(cx.listener(|_, _action: &ActionB, _cx| {
|
||||||
println!("Action B dispatched on child 1 during");
|
println!("Action B dispatched on child 1 during");
|
||||||
}))
|
}))
|
||||||
.w_full()
|
.w_full()
|
||||||
|
@ -83,7 +82,7 @@ impl Render for FocusStory {
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.child_2_focus)
|
.track_focus(&self.child_2_focus)
|
||||||
.key_context("child-2")
|
.key_context("child-2")
|
||||||
.on_action(cx.listener(|_, action: &ActionC, cx| {
|
.on_action(cx.listener(|_, _action: &ActionC, _cx| {
|
||||||
println!("Action C dispatched on child 2");
|
println!("Action C dispatched on child 2");
|
||||||
}))
|
}))
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct KitchenSinkStory;
|
||||||
|
|
||||||
impl KitchenSinkStory {
|
impl KitchenSinkStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.build_view(|cx| Self)
|
cx.build_view(|_cx| Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::*, AnyElement, Div, KeyBinding, Render, SharedString, Styled, Task, View,
|
div, prelude::*, Div, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext,
|
||||||
WindowContext,
|
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme2::ActiveTheme;
|
use theme2::ActiveTheme;
|
||||||
|
use ui::{Label, ListItem};
|
||||||
|
|
||||||
pub struct PickerStory {
|
pub struct PickerStory {
|
||||||
picker: View<Picker<Delegate>>,
|
picker: View<Picker<Delegate>>,
|
||||||
|
@ -37,6 +37,8 @@ impl Delegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for Delegate {
|
impl PickerDelegate for Delegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
self.candidates.len()
|
self.candidates.len()
|
||||||
}
|
}
|
||||||
|
@ -49,27 +51,20 @@ impl PickerDelegate for Delegate {
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut gpui::ViewContext<Picker<Self>>,
|
_cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement {
|
) -> Option<Self::ListItem> {
|
||||||
let colors = cx.theme().colors();
|
|
||||||
let Some(candidate_ix) = self.matches.get(ix) else {
|
let Some(candidate_ix) = self.matches.get(ix) else {
|
||||||
return div().into_any();
|
return None;
|
||||||
};
|
};
|
||||||
// TASK: Make StringMatchCandidate::string a SharedString
|
// TASK: Make StringMatchCandidate::string a SharedString
|
||||||
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
|
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
|
||||||
|
|
||||||
div()
|
Some(
|
||||||
.text_color(colors.text)
|
ListItem::new(ix)
|
||||||
.when(selected, |s| {
|
.inset(true)
|
||||||
s.border_l_10().border_color(colors.terminal_ansi_yellow)
|
.selected(selected)
|
||||||
})
|
.child(Label::new(candidate)),
|
||||||
.hover(|style| {
|
)
|
||||||
style
|
|
||||||
.bg(colors.element_active)
|
|
||||||
.text_color(colors.text_accent)
|
|
||||||
})
|
|
||||||
.child(candidate)
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
fn selected_index(&self) -> usize {
|
||||||
|
@ -81,7 +76,7 @@ impl PickerDelegate for Delegate {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
fn confirm(&mut self, secondary: bool, _cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||||
let candidate_ix = self.matches[self.selected_ix];
|
let candidate_ix = self.matches[self.selected_ix];
|
||||||
let candidate = self.candidates[candidate_ix].string.clone();
|
let candidate = self.candidates[candidate_ix].string.clone();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub struct ScrollStory;
|
||||||
|
|
||||||
impl ScrollStory {
|
impl ScrollStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
|
pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
|
||||||
cx.build_view(|cx| ScrollStory)
|
cx.build_view(|_cx| ScrollStory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub struct TextStory;
|
||||||
|
|
||||||
impl TextStory {
|
impl TextStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.build_view(|cx| Self)
|
cx.build_view(|_cx| Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ impl Render for TextStory {
|
||||||
cx.text_style().to_run(18),
|
cx.text_style().to_run(18),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, cx| {
|
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||||
println!("Clicked range {range_ix}");
|
println!("Clicked range {range_ix}");
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct ZIndexStory;
|
||||||
impl Render for ZIndexStory {
|
impl Render for ZIndexStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container().child(Story::title("z-index")).child(
|
Story::container().child(Story::title("z-index")).child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -84,7 +84,7 @@ struct ZIndexExample {
|
||||||
impl RenderOnce for ZIndexExample {
|
impl RenderOnce for ZIndexExample {
|
||||||
type Rendered = Div;
|
type Rendered = Div;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
div()
|
div()
|
||||||
.relative()
|
.relative()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub enum ComponentStory {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Focus,
|
Focus,
|
||||||
Icon,
|
Icon,
|
||||||
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
Keybinding,
|
Keybinding,
|
||||||
Label,
|
Label,
|
||||||
|
@ -37,6 +38,7 @@ impl ComponentStory {
|
||||||
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
|
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
|
||||||
Self::Focus => FocusStory::view(cx).into(),
|
Self::Focus => FocusStory::view(cx).into(),
|
||||||
Self::Icon => cx.build_view(|_| ui::IconStory).into(),
|
Self::Icon => cx.build_view(|_| ui::IconStory).into(),
|
||||||
|
Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
|
||||||
Self::Input => cx.build_view(|_| ui::InputStory).into(),
|
Self::Input => cx.build_view(|_| ui::InputStory).into(),
|
||||||
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
|
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
|
||||||
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
|
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![allow(dead_code, unused_variables)]
|
|
||||||
|
|
||||||
mod assets;
|
mod assets;
|
||||||
mod stories;
|
mod stories;
|
||||||
mod story_selector;
|
mod story_selector;
|
||||||
|
@ -70,7 +68,7 @@ fn main() {
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
|
|
||||||
let window = cx.open_window(
|
let _window = cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: WindowBounds::Fixed(Bounds {
|
bounds: WindowBounds::Fixed(Bounds {
|
||||||
origin: Default::default(),
|
origin: Default::default(),
|
||||||
|
@ -104,7 +102,7 @@ impl StoryWrapper {
|
||||||
impl Render for StoryWrapper {
|
impl Render for StoryWrapper {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
|
|
@ -23,15 +23,15 @@ impl ThemeColors {
|
||||||
surface_background: neutral().light().step_2(),
|
surface_background: neutral().light().step_2(),
|
||||||
background: neutral().light().step_1(),
|
background: neutral().light().step_1(),
|
||||||
element_background: neutral().light().step_3(),
|
element_background: neutral().light().step_3(),
|
||||||
element_hover: neutral().light().step_4(),
|
element_hover: neutral().light_alpha().step_4(),
|
||||||
element_active: neutral().light().step_5(),
|
element_active: neutral().light_alpha().step_5(),
|
||||||
element_selected: neutral().light().step_5(),
|
element_selected: neutral().light_alpha().step_5(),
|
||||||
element_disabled: neutral().light_alpha().step_3(),
|
element_disabled: neutral().light_alpha().step_3(),
|
||||||
drop_target_background: blue().light_alpha().step_2(),
|
drop_target_background: blue().light_alpha().step_2(),
|
||||||
ghost_element_background: system.transparent,
|
ghost_element_background: system.transparent,
|
||||||
ghost_element_hover: neutral().light().step_4(),
|
ghost_element_hover: neutral().light_alpha().step_4(),
|
||||||
ghost_element_active: neutral().light().step_5(),
|
ghost_element_active: neutral().light_alpha().step_5(),
|
||||||
ghost_element_selected: neutral().light().step_5(),
|
ghost_element_selected: neutral().light_alpha().step_5(),
|
||||||
ghost_element_disabled: neutral().light_alpha().step_3(),
|
ghost_element_disabled: neutral().light_alpha().step_3(),
|
||||||
text: yellow().light().step_9(),
|
text: yellow().light().step_9(),
|
||||||
text_muted: neutral().light().step_11(),
|
text_muted: neutral().light().step_11(),
|
||||||
|
@ -95,15 +95,15 @@ impl ThemeColors {
|
||||||
surface_background: neutral().dark().step_2(),
|
surface_background: neutral().dark().step_2(),
|
||||||
background: neutral().dark().step_1(),
|
background: neutral().dark().step_1(),
|
||||||
element_background: neutral().dark().step_3(),
|
element_background: neutral().dark().step_3(),
|
||||||
element_hover: neutral().dark().step_4(),
|
element_hover: neutral().dark_alpha().step_4(),
|
||||||
element_active: neutral().dark().step_5(),
|
element_active: neutral().dark_alpha().step_5(),
|
||||||
element_selected: neutral().dark().step_5(),
|
element_selected: neutral().dark_alpha().step_5(),
|
||||||
element_disabled: neutral().dark_alpha().step_3(),
|
element_disabled: neutral().dark_alpha().step_3(),
|
||||||
drop_target_background: blue().dark_alpha().step_2(),
|
drop_target_background: blue().dark_alpha().step_2(),
|
||||||
ghost_element_background: system.transparent,
|
ghost_element_background: system.transparent,
|
||||||
ghost_element_hover: neutral().dark().step_4(),
|
ghost_element_hover: neutral().dark_alpha().step_4(),
|
||||||
ghost_element_active: neutral().dark().step_5(),
|
ghost_element_active: neutral().dark_alpha().step_5(),
|
||||||
ghost_element_selected: neutral().dark().step_5(),
|
ghost_element_selected: neutral().dark_alpha().step_5(),
|
||||||
ghost_element_disabled: neutral().dark_alpha().step_3(),
|
ghost_element_disabled: neutral().dark_alpha().step_3(),
|
||||||
text: neutral().dark().step_12(),
|
text: neutral().dark().step_12(),
|
||||||
text_muted: neutral().dark().step_11(),
|
text_muted: neutral().dark().step_11(),
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub fn one_family() -> ThemeFamily {
|
||||||
pub(crate) fn one_dark() -> Theme {
|
pub(crate) fn one_dark() -> Theme {
|
||||||
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
|
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
|
||||||
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
|
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
|
||||||
let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
|
let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.);
|
||||||
|
|
||||||
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
|
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
|
||||||
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
|
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
|
||||||
|
@ -48,7 +48,7 @@ pub(crate) fn one_dark() -> Theme {
|
||||||
elevated_surface_background: elevated_surface,
|
elevated_surface_background: elevated_surface,
|
||||||
surface_background: bg,
|
surface_background: bg,
|
||||||
background: bg,
|
background: bg,
|
||||||
element_background: elevated_surface,
|
element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0),
|
||||||
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
|
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_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_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
|
||||||
|
|
|
@ -13,6 +13,7 @@ editor = { package = "editor2", path = "../editor2" }
|
||||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||||
fs = { package = "fs2", path = "../fs2" }
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
picker = { package = "picker2", path = "../picker2" }
|
picker = { package = "picker2", path = "../picker2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
|
|
|
@ -2,19 +2,16 @@ use feature_flags::FeatureFlagAppExt;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, AnyElement, AppContext, DismissEvent, Element, EventEmitter, FocusableView,
|
actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, View,
|
SharedString, View, ViewContext, VisualContext, WeakView,
|
||||||
ViewContext, VisualContext, WeakView,
|
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use settings::{update_settings_file, SettingsStore};
|
use settings::{update_settings_file, SettingsStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
|
use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
|
||||||
|
use ui::ListItem;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{
|
use workspace::{ui::HighlightedLabel, Workspace};
|
||||||
ui::{HighlightedLabel, StyledExt},
|
|
||||||
Workspace,
|
|
||||||
};
|
|
||||||
|
|
||||||
actions!(Toggle, Reload);
|
actions!(Toggle, Reload);
|
||||||
|
|
||||||
|
@ -160,6 +157,8 @@ impl ThemeSelectorDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for ThemeSelectorDelegate {
|
impl PickerDelegate for ThemeSelectorDelegate {
|
||||||
|
type ListItem = ui::ListItem;
|
||||||
|
|
||||||
fn placeholder_text(&self) -> Arc<str> {
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
"Select Theme...".into()
|
"Select Theme...".into()
|
||||||
}
|
}
|
||||||
|
@ -260,24 +259,18 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
_cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement {
|
) -> Option<Self::ListItem> {
|
||||||
let theme = cx.theme();
|
|
||||||
let colors = theme.colors();
|
|
||||||
|
|
||||||
let theme_match = &self.matches[ix];
|
let theme_match = &self.matches[ix];
|
||||||
div()
|
|
||||||
.px_1()
|
Some(
|
||||||
.text_color(colors.text)
|
ListItem::new(ix)
|
||||||
.text_ui()
|
.inset(true)
|
||||||
.bg(colors.ghost_element_background)
|
.selected(selected)
|
||||||
.rounded_md()
|
.child(HighlightedLabel::new(
|
||||||
.when(selected, |this| this.bg(colors.ghost_element_selected))
|
theme_match.string.clone(),
|
||||||
.hover(|this| this.bg(colors.ghost_element_hover))
|
theme_match.positions.clone(),
|
||||||
.child(HighlightedLabel::new(
|
)),
|
||||||
theme_match.string.clone(),
|
)
|
||||||
theme_match.positions.clone(),
|
|
||||||
))
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
itertools = { version = "0.11.0", optional = true }
|
itertools = { version = "0.11.0", optional = true }
|
||||||
menu = { package = "menu2", path = "../menu2"}
|
menu = { package = "menu2", path = "../menu2"}
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings2 = { path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
story = { path = "../story", optional = true }
|
story = { path = "../story", optional = true }
|
||||||
strum = { version = "0.25.0", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
theme2 = { path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -49,6 +49,12 @@ impl Avatar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn source(src: ImageSource) -> Self {
|
||||||
|
Self {
|
||||||
|
src,
|
||||||
|
shape: Shape::Circle,
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn shape(mut self, shape: Shape) -> Self {
|
pub fn shape(mut self, shape: Shape) -> Self {
|
||||||
self.shape = shape;
|
self.shape = shape;
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use gpui::{div, prelude::*, Div, Element, ElementId, IntoElement, Styled, WindowContext};
|
use gpui::{div, prelude::*, Div, Element, ElementId, IntoElement, Styled, WindowContext};
|
||||||
|
|
||||||
use theme2::ActiveTheme;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::{Color, Icon, IconElement, Selection};
|
use crate::{Color, Icon, IconElement, Selection};
|
||||||
|
|
||||||
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
|
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
|
||||||
|
|
|
@ -264,7 +264,7 @@ impl<M: ManagedView> Element for MenuHandle<M> {
|
||||||
|
|
||||||
let new_menu = (builder)(cx);
|
let new_menu = (builder)(cx);
|
||||||
let menu2 = menu.clone();
|
let menu2 = menu.clone();
|
||||||
cx.subscribe(&new_menu, move |modal, e, cx| match e {
|
cx.subscribe(&new_menu, move |_modal, e, cx| match e {
|
||||||
&DismissEvent::Dismiss => {
|
&DismissEvent::Dismiss => {
|
||||||
*menu2.borrow_mut() = None;
|
*menu2.borrow_mut() = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
@ -49,17 +49,4 @@ impl Divider {
|
||||||
self.inset = true;
|
self.inset = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl Element {
|
|
||||||
div()
|
|
||||||
.map(|this| match self.direction {
|
|
||||||
DividerDirection::Horizontal => {
|
|
||||||
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
|
|
||||||
}
|
|
||||||
DividerDirection::Vertical => {
|
|
||||||
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.bg(cx.theme().colors().border_variant)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ pub enum IconSize {
|
||||||
pub enum Icon {
|
pub enum Icon {
|
||||||
Ai,
|
Ai,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowDown,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
AtSign,
|
AtSign,
|
||||||
|
@ -61,6 +63,7 @@ pub enum Icon {
|
||||||
Mic,
|
Mic,
|
||||||
MicMute,
|
MicMute,
|
||||||
Plus,
|
Plus,
|
||||||
|
Public,
|
||||||
Quote,
|
Quote,
|
||||||
Replace,
|
Replace,
|
||||||
ReplaceAll,
|
ReplaceAll,
|
||||||
|
@ -71,6 +74,11 @@ pub enum Icon {
|
||||||
Terminal,
|
Terminal,
|
||||||
WholeWord,
|
WholeWord,
|
||||||
XCircle,
|
XCircle,
|
||||||
|
Command,
|
||||||
|
Control,
|
||||||
|
Shift,
|
||||||
|
Option,
|
||||||
|
Return,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Icon {
|
impl Icon {
|
||||||
|
@ -79,6 +87,8 @@ impl Icon {
|
||||||
Icon::Ai => "icons/ai.svg",
|
Icon::Ai => "icons/ai.svg",
|
||||||
Icon::ArrowLeft => "icons/arrow_left.svg",
|
Icon::ArrowLeft => "icons/arrow_left.svg",
|
||||||
Icon::ArrowRight => "icons/arrow_right.svg",
|
Icon::ArrowRight => "icons/arrow_right.svg",
|
||||||
|
Icon::ArrowUp => "icons/arrow_up.svg",
|
||||||
|
Icon::ArrowDown => "icons/arrow_down.svg",
|
||||||
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
|
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
|
||||||
Icon::AtSign => "icons/at-sign.svg",
|
Icon::AtSign => "icons/at-sign.svg",
|
||||||
Icon::AudioOff => "icons/speaker-off.svg",
|
Icon::AudioOff => "icons/speaker-off.svg",
|
||||||
|
@ -125,6 +135,7 @@ impl Icon {
|
||||||
Icon::Mic => "icons/mic.svg",
|
Icon::Mic => "icons/mic.svg",
|
||||||
Icon::MicMute => "icons/mic-mute.svg",
|
Icon::MicMute => "icons/mic-mute.svg",
|
||||||
Icon::Plus => "icons/plus.svg",
|
Icon::Plus => "icons/plus.svg",
|
||||||
|
Icon::Public => "icons/public.svg",
|
||||||
Icon::Quote => "icons/quote.svg",
|
Icon::Quote => "icons/quote.svg",
|
||||||
Icon::Replace => "icons/replace.svg",
|
Icon::Replace => "icons/replace.svg",
|
||||||
Icon::ReplaceAll => "icons/replace_all.svg",
|
Icon::ReplaceAll => "icons/replace_all.svg",
|
||||||
|
@ -135,6 +146,11 @@ impl Icon {
|
||||||
Icon::Terminal => "icons/terminal.svg",
|
Icon::Terminal => "icons/terminal.svg",
|
||||||
Icon::WholeWord => "icons/word_search.svg",
|
Icon::WholeWord => "icons/word_search.svg",
|
||||||
Icon::XCircle => "icons/error.svg",
|
Icon::XCircle => "icons/error.svg",
|
||||||
|
Icon::Command => "icons/command.svg",
|
||||||
|
Icon::Control => "icons/control.svg",
|
||||||
|
Icon::Shift => "icons/shift.svg",
|
||||||
|
Icon::Option => "icons/option.svg",
|
||||||
|
Icon::Return => "icons/return.svg",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,8 +167,8 @@ impl RenderOnce for IconElement {
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||||
let svg_size = match self.size {
|
let svg_size = match self.size {
|
||||||
IconSize::Small => rems(0.75),
|
IconSize::Small => rems(14. / 16.),
|
||||||
IconSize::Medium => rems(0.9375),
|
IconSize::Medium => rems(16. / 16.),
|
||||||
};
|
};
|
||||||
|
|
||||||
svg()
|
svg()
|
||||||
|
@ -189,17 +205,4 @@ impl IconElement {
|
||||||
self.size = size;
|
self.size = size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl Element {
|
|
||||||
let svg_size = match self.size {
|
|
||||||
IconSize::Small => rems(0.75),
|
|
||||||
IconSize::Medium => rems(0.9375),
|
|
||||||
};
|
|
||||||
|
|
||||||
svg()
|
|
||||||
.size(svg_size)
|
|
||||||
.flex_none()
|
|
||||||
.path(self.path)
|
|
||||||
.text_color(self.color.color(cx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,13 @@ impl RenderOnce for IconButton {
|
||||||
_ => self.color,
|
_ => self.color,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
|
let (mut bg_color, bg_active_color) = match self.variant {
|
||||||
ButtonVariant::Filled => (
|
ButtonVariant::Filled => (
|
||||||
cx.theme().colors().element_background,
|
cx.theme().colors().element_background,
|
||||||
cx.theme().colors().element_hover,
|
|
||||||
cx.theme().colors().element_active,
|
cx.theme().colors().element_active,
|
||||||
),
|
),
|
||||||
ButtonVariant::Ghost => (
|
ButtonVariant::Ghost => (
|
||||||
cx.theme().colors().ghost_element_background,
|
cx.theme().colors().ghost_element_background,
|
||||||
cx.theme().colors().ghost_element_hover,
|
|
||||||
cx.theme().colors().ghost_element_active,
|
cx.theme().colors().ghost_element_active,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -67,7 +65,8 @@ impl RenderOnce for IconButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button
|
// HACK: Add an additional identified element wrapper to fix tooltips not showing up.
|
||||||
|
div().id(self.id.clone()).child(button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +123,6 @@ impl IconButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(self, action: Box<dyn Action>) -> Self {
|
pub fn action(self, action: Box<dyn Action>) -> Self {
|
||||||
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
|
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
|
||||||
use gpui::{Action, Div, IntoElement};
|
use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
|
||||||
|
|
||||||
#[derive(IntoElement, Clone)]
|
#[derive(IntoElement, Clone)]
|
||||||
pub struct KeyBinding {
|
pub struct KeyBinding {
|
||||||
|
@ -14,19 +14,35 @@ impl RenderOnce for KeyBinding {
|
||||||
type Rendered = Div;
|
type Rendered = Div;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||||
div()
|
h_stack()
|
||||||
.flex()
|
.flex_none()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
|
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
|
||||||
div()
|
let key_icon = Self::icon_for_key(&keystroke);
|
||||||
.flex()
|
|
||||||
.gap_1()
|
h_stack()
|
||||||
|
.flex_none()
|
||||||
|
.gap_0p5()
|
||||||
|
.bg(cx.theme().colors().element_background)
|
||||||
|
.p_0p5()
|
||||||
|
.rounded_sm()
|
||||||
.when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
|
.when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
|
||||||
.when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
|
.when(keystroke.modifiers.control, |el| {
|
||||||
.when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥")))
|
el.child(KeyIcon::new(Icon::Control))
|
||||||
.when(keystroke.modifiers.command, |el| el.child(Key::new("⌘")))
|
})
|
||||||
.when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧")))
|
.when(keystroke.modifiers.alt, |el| {
|
||||||
.child(Key::new(keystroke.key.clone()))
|
el.child(KeyIcon::new(Icon::Option))
|
||||||
|
})
|
||||||
|
.when(keystroke.modifiers.command, |el| {
|
||||||
|
el.child(KeyIcon::new(Icon::Command))
|
||||||
|
})
|
||||||
|
.when(keystroke.modifiers.shift, |el| {
|
||||||
|
el.child(KeyIcon::new(Icon::Shift))
|
||||||
|
})
|
||||||
|
.when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
|
||||||
|
.when(key_icon.is_none(), |el| {
|
||||||
|
el.child(Key::new(keystroke.key.to_uppercase().clone()))
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +55,22 @@ impl KeyBinding {
|
||||||
Some(Self::new(key_binding))
|
Some(Self::new(key_binding))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
|
||||||
|
let mut icon: Option<Icon> = None;
|
||||||
|
|
||||||
|
if keystroke.key == "left".to_string() {
|
||||||
|
icon = Some(Icon::ArrowLeft);
|
||||||
|
} else if keystroke.key == "right".to_string() {
|
||||||
|
icon = Some(Icon::ArrowRight);
|
||||||
|
} else if keystroke.key == "up".to_string() {
|
||||||
|
icon = Some(Icon::ArrowUp);
|
||||||
|
} else if keystroke.key == "down".to_string() {
|
||||||
|
icon = Some(Icon::ArrowDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(key_binding: gpui::KeyBinding) -> Self {
|
pub fn new(key_binding: gpui::KeyBinding) -> Self {
|
||||||
Self { key_binding }
|
Self { key_binding }
|
||||||
}
|
}
|
||||||
|
@ -53,13 +85,18 @@ impl RenderOnce for Key {
|
||||||
type Rendered = Div;
|
type Rendered = Div;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
let single_char = self.key.len() == 1;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.px_2()
|
|
||||||
.py_0()
|
.py_0()
|
||||||
.rounded_md()
|
.when(single_char, |el| {
|
||||||
.text_ui_sm()
|
el.w(rems(14. / 16.)).flex().flex_none().justify_center()
|
||||||
|
})
|
||||||
|
.when(!single_char, |el| el.px_0p5())
|
||||||
|
.h(rems(14. / 16.))
|
||||||
|
.text_ui()
|
||||||
|
.line_height(relative(1.))
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.bg(cx.theme().colors().element_background)
|
|
||||||
.child(self.key.clone())
|
.child(self.key.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,3 +106,24 @@ impl Key {
|
||||||
Self { key: key.into() }
|
Self { key: key.into() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct KeyIcon {
|
||||||
|
icon: Icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for KeyIcon {
|
||||||
|
type Rendered = Div;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
div()
|
||||||
|
.w(rems(14. / 16.))
|
||||||
|
.child(IconElement::new(self.icon).size(IconSize::Small))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyIcon {
|
||||||
|
pub fn new(icon: Icon) -> Self {
|
||||||
|
Self { icon }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::styled_ext::StyledExt;
|
use crate::styled_ext::StyledExt;
|
||||||
use gpui::{relative, Div, Hsla, IntoElement, StyledText, TextRun, WindowContext};
|
use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
||||||
pub enum LabelSize {
|
pub enum LabelSize {
|
||||||
|
@ -182,9 +182,3 @@ impl HighlightedLabel {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A run of text that receives the same style.
|
|
||||||
struct Run {
|
|
||||||
pub text: String,
|
|
||||||
pub color: Hsla,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
use gpui::{
|
|
||||||
div, px, AnyElement, ClickEvent, Div, IntoElement, Stateful, StatefulInteractiveElement,
|
|
||||||
};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent,
|
||||||
|
Pixels, Stateful, StatefulInteractiveElement,
|
||||||
|
};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
|
disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label,
|
||||||
|
Toggle,
|
||||||
};
|
};
|
||||||
use crate::{prelude::*, GraphicSlot};
|
use crate::{prelude::*, GraphicSlot};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Debug, PartialEq)]
|
|
||||||
pub enum ListItemVariant {
|
|
||||||
/// The list item extends to the far left and right of the list.
|
|
||||||
FullWidth,
|
|
||||||
#[default]
|
|
||||||
Inset,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ListHeaderMeta {
|
pub enum ListHeaderMeta {
|
||||||
// TODO: These should be IconButtons
|
Tools(Vec<IconButton>),
|
||||||
Tools(Vec<Icon>),
|
|
||||||
// TODO: This should be a button
|
// TODO: This should be a button
|
||||||
Button(Label),
|
Button(Label),
|
||||||
Text(Label),
|
Text(Label),
|
||||||
|
@ -30,8 +24,39 @@ pub struct ListHeader {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
left_icon: Option<Icon>,
|
left_icon: Option<Icon>,
|
||||||
meta: Option<ListHeaderMeta>,
|
meta: Option<ListHeaderMeta>,
|
||||||
variant: ListItemVariant,
|
|
||||||
toggle: Toggle,
|
toggle: Toggle,
|
||||||
|
inset: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListHeader {
|
||||||
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
left_icon: None,
|
||||||
|
meta: None,
|
||||||
|
inset: false,
|
||||||
|
toggle: Toggle::NotToggleable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||||
|
self.toggle = toggle;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||||
|
self.left_icon = left_icon;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right_button(self, button: IconButton) -> Self {
|
||||||
|
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
|
||||||
|
self.meta = meta;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for ListHeader {
|
impl RenderOnce for ListHeader {
|
||||||
|
@ -45,11 +70,7 @@ impl RenderOnce for ListHeader {
|
||||||
h_stack()
|
h_stack()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.items_center()
|
.items_center()
|
||||||
.children(icons.into_iter().map(|i| {
|
.children(icons.into_iter().map(|i| i.color(Color::Muted))),
|
||||||
IconElement::new(i)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
})),
|
|
||||||
),
|
),
|
||||||
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||||
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||||
|
@ -63,7 +84,7 @@ impl RenderOnce for ListHeader {
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.h_5()
|
.h_5()
|
||||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
.when(self.inset, |this| this.px_2())
|
||||||
.flex()
|
.flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
@ -92,98 +113,11 @@ impl RenderOnce for ListHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListHeader {
|
|
||||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
|
||||||
Self {
|
|
||||||
label: label.into(),
|
|
||||||
left_icon: None,
|
|
||||||
meta: None,
|
|
||||||
variant: ListItemVariant::default(),
|
|
||||||
toggle: Toggle::NotToggleable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
|
||||||
self.toggle = toggle;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
|
||||||
self.left_icon = left_icon;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
|
|
||||||
self.meta = meta;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// before_ship!("delete")
|
|
||||||
// fn render<V: 'static>(self, cx: &mut WindowContext) -> impl Element<V> {
|
|
||||||
// let disclosure_control = disclosure_control(self.toggle);
|
|
||||||
|
|
||||||
// let meta = match self.meta {
|
|
||||||
// Some(ListHeaderMeta::Tools(icons)) => div().child(
|
|
||||||
// h_stack()
|
|
||||||
// .gap_2()
|
|
||||||
// .items_center()
|
|
||||||
// .children(icons.into_iter().map(|i| {
|
|
||||||
// IconElement::new(i)
|
|
||||||
// .color(TextColor::Muted)
|
|
||||||
// .size(IconSize::Small)
|
|
||||||
// })),
|
|
||||||
// ),
|
|
||||||
// Some(ListHeaderMeta::Button(label)) => div().child(label),
|
|
||||||
// Some(ListHeaderMeta::Text(label)) => div().child(label),
|
|
||||||
// None => div(),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// h_stack()
|
|
||||||
// .w_full()
|
|
||||||
// .bg(cx.theme().colors().surface_background)
|
|
||||||
// // TODO: Add focus state
|
|
||||||
// // .when(self.state == InteractionState::Focused, |this| {
|
|
||||||
// // this.border()
|
|
||||||
// // .border_color(cx.theme().colors().border_focused)
|
|
||||||
// // })
|
|
||||||
// .relative()
|
|
||||||
// .child(
|
|
||||||
// div()
|
|
||||||
// .h_5()
|
|
||||||
// .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
|
||||||
// .flex()
|
|
||||||
// .flex_1()
|
|
||||||
// .items_center()
|
|
||||||
// .justify_between()
|
|
||||||
// .w_full()
|
|
||||||
// .gap_1()
|
|
||||||
// .child(
|
|
||||||
// h_stack()
|
|
||||||
// .gap_1()
|
|
||||||
// .child(
|
|
||||||
// div()
|
|
||||||
// .flex()
|
|
||||||
// .gap_1()
|
|
||||||
// .items_center()
|
|
||||||
// .children(self.left_icon.map(|i| {
|
|
||||||
// IconElement::new(i)
|
|
||||||
// .color(TextColor::Muted)
|
|
||||||
// .size(IconSize::Small)
|
|
||||||
// }))
|
|
||||||
// .child(Label::new(self.label.clone()).color(TextColor::Muted)),
|
|
||||||
// )
|
|
||||||
// .child(disclosure_control),
|
|
||||||
// )
|
|
||||||
// .child(meta),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement, Clone)]
|
#[derive(IntoElement, Clone)]
|
||||||
pub struct ListSubHeader {
|
pub struct ListSubHeader {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
left_icon: Option<Icon>,
|
left_icon: Option<Icon>,
|
||||||
variant: ListItemVariant,
|
inset: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListSubHeader {
|
impl ListSubHeader {
|
||||||
|
@ -191,7 +125,7 @@ impl ListSubHeader {
|
||||||
Self {
|
Self {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
left_icon: None,
|
left_icon: None,
|
||||||
variant: ListItemVariant::default(),
|
inset: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,11 +138,11 @@ impl ListSubHeader {
|
||||||
impl RenderOnce for ListSubHeader {
|
impl RenderOnce for ListSubHeader {
|
||||||
type Rendered = Div;
|
type Rendered = Div;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
h_stack().flex_1().w_full().relative().py_1().child(
|
h_stack().flex_1().w_full().relative().py_1().child(
|
||||||
div()
|
div()
|
||||||
.h_6()
|
.h_6()
|
||||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
.when(self.inset, |this| this.px_2())
|
||||||
.flex()
|
.flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
@ -231,26 +165,19 @@ impl RenderOnce for ListSubHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
|
||||||
pub enum ListEntrySize {
|
|
||||||
#[default]
|
|
||||||
Small,
|
|
||||||
Medium,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
disabled: bool,
|
selected: bool,
|
||||||
// TODO: Reintroduce this
|
// TODO: Reintroduce this
|
||||||
// disclosure_control_style: DisclosureControlVisibility,
|
// disclosure_control_style: DisclosureControlVisibility,
|
||||||
indent_level: u32,
|
indent_level: usize,
|
||||||
|
indent_step_size: Pixels,
|
||||||
left_slot: Option<GraphicSlot>,
|
left_slot: Option<GraphicSlot>,
|
||||||
overflow: OverflowStyle,
|
|
||||||
size: ListEntrySize,
|
|
||||||
toggle: Toggle,
|
toggle: Toggle,
|
||||||
variant: ListItemVariant,
|
inset: bool,
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
|
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,14 +185,14 @@ impl ListItem {
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
disabled: false,
|
selected: false,
|
||||||
indent_level: 0,
|
indent_level: 0,
|
||||||
|
indent_step_size: px(12.),
|
||||||
left_slot: None,
|
left_slot: None,
|
||||||
overflow: OverflowStyle::Hidden,
|
|
||||||
size: ListEntrySize::default(),
|
|
||||||
toggle: Toggle::NotToggleable,
|
toggle: Toggle::NotToggleable,
|
||||||
variant: ListItemVariant::default(),
|
inset: false,
|
||||||
on_click: Default::default(),
|
on_click: None,
|
||||||
|
on_secondary_mouse_down: None,
|
||||||
children: SmallVec::new(),
|
children: SmallVec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,21 +202,39 @@ impl ListItem {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variant(mut self, variant: ListItemVariant) -> Self {
|
pub fn on_secondary_mouse_down(
|
||||||
self.variant = variant;
|
mut self,
|
||||||
|
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_secondary_mouse_down = Some(Rc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indent_level(mut self, indent_level: u32) -> Self {
|
pub fn inset(mut self, inset: bool) -> Self {
|
||||||
|
self.inset = inset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indent_level(mut self, indent_level: usize) -> Self {
|
||||||
self.indent_level = indent_level;
|
self.indent_level = indent_level;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
|
||||||
|
self.indent_step_size = indent_step_size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||||
self.toggle = toggle;
|
self.toggle = toggle;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
|
pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
|
||||||
self.left_slot = Some(left_content);
|
self.left_slot = Some(left_content);
|
||||||
self
|
self
|
||||||
|
@ -300,15 +245,10 @@ impl ListItem {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self {
|
pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
|
||||||
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
|
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(mut self, size: ListEntrySize) -> Self {
|
|
||||||
self.size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for ListItem {
|
impl RenderOnce for ListItem {
|
||||||
|
@ -323,61 +263,64 @@ impl RenderOnce for ListItem {
|
||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::uri(src))),
|
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::source(src))),
|
||||||
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))),
|
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let sized_item = match self.size {
|
|
||||||
ListEntrySize::Small => div().h_6(),
|
|
||||||
ListEntrySize::Medium => div().h_7(),
|
|
||||||
};
|
|
||||||
div()
|
div()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.relative()
|
.relative()
|
||||||
.hover(|mut style| {
|
|
||||||
style.background = Some(cx.theme().colors().editor_background.into());
|
|
||||||
style
|
|
||||||
})
|
|
||||||
.on_click({
|
|
||||||
let on_click = self.on_click.clone();
|
|
||||||
move |event, cx| {
|
|
||||||
if let Some(on_click) = &on_click {
|
|
||||||
(on_click)(event, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// TODO: Add focus state
|
// TODO: Add focus state
|
||||||
// .when(self.state == InteractionState::Focused, |this| {
|
// .when(self.state == InteractionState::Focused, |this| {
|
||||||
// this.border()
|
// this.border()
|
||||||
// .border_color(cx.theme().colors().border_focused)
|
// .border_color(cx.theme().colors().border_focused)
|
||||||
// })
|
// })
|
||||||
|
.when(self.inset, |this| this.rounded_md())
|
||||||
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||||
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
|
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
|
||||||
|
.when(self.selected, |this| {
|
||||||
|
this.bg(cx.theme().colors().ghost_element_selected)
|
||||||
|
})
|
||||||
|
.when_some(self.on_click.clone(), |this, on_click| {
|
||||||
|
this.on_click(move |event, cx| {
|
||||||
|
// HACK: GPUI currently fires `on_click` with any mouse button,
|
||||||
|
// but we only care about the left button.
|
||||||
|
if event.down.button == MouseButton::Left {
|
||||||
|
(on_click)(event, cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
|
||||||
|
this.on_mouse_down(MouseButton::Right, move |event, cx| {
|
||||||
|
(on_mouse_down)(event, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
sized_item
|
div()
|
||||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
.when(self.inset, |this| this.px_2())
|
||||||
// .ml(rems(0.75 * self.indent_level as f32))
|
.ml(self.indent_level as f32 * self.indent_step_size)
|
||||||
.children((0..self.indent_level).map(|_| {
|
|
||||||
div()
|
|
||||||
.w(px(4.))
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.justify_center()
|
|
||||||
.group_hover("", |style| style.bg(cx.theme().colors().border_focused))
|
|
||||||
.child(
|
|
||||||
h_stack()
|
|
||||||
.child(div().w_px().h_full())
|
|
||||||
.child(div().w_px().h_full().bg(cx.theme().colors().border)),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.flex()
|
.flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.items_center()
|
.items_center()
|
||||||
.relative()
|
.relative()
|
||||||
.child(disclosure_control(self.toggle))
|
.child(disclosure_control(self.toggle))
|
||||||
.children(left_content)
|
.children(left_content)
|
||||||
.children(self.children),
|
.children(self.children)
|
||||||
|
// HACK: We need to attach the `on_click` handler to the child element in order to have the click
|
||||||
|
// event actually fire.
|
||||||
|
// Once this is fixed in GPUI we can remove this and rely on the `on_click` handler set above on the
|
||||||
|
// outer `div`.
|
||||||
|
.id("on_click_hack")
|
||||||
|
.when_some(self.on_click, |this, on_click| {
|
||||||
|
this.on_click(move |event, cx| {
|
||||||
|
// HACK: GPUI currently fires `on_click` with any mouse button,
|
||||||
|
// but we only care about the left button.
|
||||||
|
if event.down.button == MouseButton::Left {
|
||||||
|
(on_click)(event, cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,7 +361,7 @@ pub struct List {
|
||||||
impl RenderOnce for List {
|
impl RenderOnce for List {
|
||||||
type Rendered = Div;
|
type Rendered = Div;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
let list_content = match (self.children.is_empty(), self.toggle) {
|
let list_content = match (self.children.is_empty(), self.toggle) {
|
||||||
(false, _) => div().children(self.children),
|
(false, _) => div().children(self.children),
|
||||||
(true, Toggle::Toggled(false)) => div(),
|
(true, Toggle::Toggled(false)) => div(),
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
|
div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{v_stack, StyledExt};
|
use crate::prelude::*;
|
||||||
|
use crate::v_stack;
|
||||||
|
|
||||||
/// A popover is used to display a menu or show some options.
|
/// A popover is used to display a menu or show some options.
|
||||||
///
|
///
|
||||||
|
@ -43,22 +44,16 @@ impl RenderOnce for Popover {
|
||||||
type Rendered = Div;
|
type Rendered = Div;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||||
v_stack()
|
div()
|
||||||
.relative()
|
.flex()
|
||||||
.elevation_2(cx)
|
.gap_1()
|
||||||
.p_1()
|
.child(v_stack().elevation_2(cx).px_1().children(self.children))
|
||||||
.children(self.children)
|
|
||||||
.when_some(self.aside, |this, aside| {
|
.when_some(self.aside, |this, aside| {
|
||||||
// TODO: This will statically position the aside to the top right of the popover.
|
|
||||||
// We should update this to use gpui2::overlay avoid collisions with the window edges.
|
|
||||||
this.child(
|
this.child(
|
||||||
v_stack()
|
v_stack()
|
||||||
.top_0()
|
|
||||||
.left_full()
|
|
||||||
.ml_1()
|
|
||||||
.absolute()
|
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.p_1()
|
.bg(cx.theme().colors().surface_background)
|
||||||
|
.px_1()
|
||||||
.child(aside),
|
.child(aside),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use gpui::SharedString;
|
use gpui::{ImageSource, SharedString};
|
||||||
|
|
||||||
use crate::Icon;
|
use crate::Icon;
|
||||||
|
|
||||||
|
@ -9,6 +9,6 @@ use crate::Icon;
|
||||||
/// Can be filled with a []
|
/// Can be filled with a []
|
||||||
pub enum GraphicSlot {
|
pub enum GraphicSlot {
|
||||||
Icon(Icon),
|
Icon(Icon),
|
||||||
Avatar(SharedString),
|
Avatar(ImageSource),
|
||||||
PublicActor(SharedString),
|
PublicActor(SharedString),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod button;
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
mod context_menu;
|
mod context_menu;
|
||||||
mod icon;
|
mod icon;
|
||||||
|
mod icon_button;
|
||||||
mod input;
|
mod input;
|
||||||
mod keybinding;
|
mod keybinding;
|
||||||
mod label;
|
mod label;
|
||||||
|
@ -13,6 +14,7 @@ pub use button::*;
|
||||||
pub use checkbox::*;
|
pub use checkbox::*;
|
||||||
pub use context_menu::*;
|
pub use context_menu::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
|
pub use icon_button::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use keybinding::*;
|
pub use keybinding::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct AvatarStory;
|
||||||
impl Render for AvatarStory {
|
impl Render for AvatarStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
Story::container()
|
||||||
.child(Story::title_for::<Avatar>())
|
.child(Story::title_for::<Avatar>())
|
||||||
.child(Story::label("Default"))
|
.child(Story::label("Default"))
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub struct ButtonStory;
|
||||||
impl Render for ButtonStory {
|
impl Render for ButtonStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let states = InteractionState::iter();
|
let states = InteractionState::iter();
|
||||||
|
|
||||||
Story::container()
|
Story::container()
|
||||||
|
@ -139,7 +139,7 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
Button::new("Label")
|
Button::new("Label")
|
||||||
.variant(ButtonVariant::Ghost)
|
.variant(ButtonVariant::Ghost)
|
||||||
.on_click(|_, cx| println!("Button clicked.")),
|
.on_click(|_, _cx| println!("Button clicked.")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
|
||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.header(header)
|
menu.header(header)
|
||||||
.separator()
|
.separator()
|
||||||
.entry("Print current time", |v, cx| {
|
.entry("Print current time", |_event, cx| {
|
||||||
println!("dispatching PrintCurrentTime action");
|
println!("dispatching PrintCurrentTime action");
|
||||||
cx.dispatch_action(PrintCurrentDate.boxed_clone())
|
cx.dispatch_action(PrintCurrentDate.boxed_clone())
|
||||||
})
|
})
|
||||||
.entry("Print best foot", |v, cx| {
|
.entry("Print best foot", |_event, cx| {
|
||||||
cx.dispatch_action(PrintBestFood.boxed_clone())
|
cx.dispatch_action(PrintBestFood.boxed_clone())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -25,7 +25,7 @@ pub struct ContextMenuStory;
|
||||||
impl Render for ContextMenuStory {
|
impl Render for ContextMenuStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
Story::container()
|
||||||
.on_action(|_: &PrintCurrentDate, _| {
|
.on_action(|_: &PrintCurrentDate, _| {
|
||||||
println!("printing unix time!");
|
println!("printing unix time!");
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub struct IconStory;
|
||||||
impl Render for IconStory {
|
impl Render for IconStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let icons = Icon::iter();
|
let icons = Icon::iter();
|
||||||
|
|
||||||
Story::container()
|
Story::container()
|
||||||
|
|
35
crates/ui2/src/components/stories/icon_button.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use gpui::{Div, Render};
|
||||||
|
use story::Story;
|
||||||
|
|
||||||
|
use crate::{prelude::*, Tooltip};
|
||||||
|
use crate::{Icon, IconButton};
|
||||||
|
|
||||||
|
pub struct IconButtonStory;
|
||||||
|
|
||||||
|
impl Render for IconButtonStory {
|
||||||
|
type Element = Div;
|
||||||
|
|
||||||
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
Story::container()
|
||||||
|
.child(Story::title_for::<IconButton>())
|
||||||
|
.child(Story::label("Default"))
|
||||||
|
.child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
|
||||||
|
.child(Story::label("With `on_click`"))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_8()
|
||||||
|
.child(
|
||||||
|
IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| {
|
||||||
|
println!("Clicked!");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(Story::label("With `tooltip`"))
|
||||||
|
.child(
|
||||||
|
div().w_8().child(
|
||||||
|
IconButton::new("with_tooltip", Icon::MessageBubbles)
|
||||||
|
.tooltip(|cx| Tooltip::text("Open messages", cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ pub struct InputStory;
|
||||||
impl Render for InputStory {
|
impl Render for InputStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
Story::container()
|
||||||
.child(Story::title_for::<Input>())
|
.child(Story::title_for::<Input>())
|
||||||
.child(Story::label("Default"))
|
.child(Story::label("Default"))
|
||||||
|
|
|
@ -16,7 +16,7 @@ pub fn binding(key: &str) -> gpui::KeyBinding {
|
||||||
impl Render for KeybindingStory {
|
impl Render for KeybindingStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
|
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
|
||||||
|
|
||||||
Story::container()
|
Story::container()
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct LabelStory;
|
||||||
impl Render for LabelStory {
|
impl Render for LabelStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
Story::container()
|
||||||
.child(Story::title_for::<Label>())
|
.child(Story::title_for::<Label>())
|
||||||
.child(Story::label("Default"))
|
.child(Story::label("Default"))
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct ListItemStory;
|
||||||
impl Render for ListItemStory {
|
impl Render for ListItemStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
Story::container()
|
||||||
.child(Story::title_for::<ListItem>())
|
.child(Story::title_for::<ListItem>())
|
||||||
.child(Story::label("Default"))
|
.child(Story::label("Default"))
|
||||||
|
@ -22,5 +22,13 @@ impl Render for ListItemStory {
|
||||||
println!("Clicked!");
|
println!("Clicked!");
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.child(Story::label("With `on_secondary_mouse_down`"))
|
||||||
|
.child(
|
||||||
|
ListItem::new("with_on_secondary_mouse_down").on_secondary_mouse_down(
|
||||||
|
|_event, _cx| {
|
||||||
|
println!("Right mouse down!");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{overlay, Action, AnyView, IntoElement, Overlay, Render, VisualContext};
|
use gpui::{overlay, Action, AnyView, IntoElement, Overlay, Render, VisualContext};
|
||||||
use settings2::Settings;
|
use settings::Settings;
|
||||||
use theme2::{ActiveTheme, ThemeSettings};
|
use theme::ThemeSettings;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt};
|
use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt};
|
||||||
|
@ -13,7 +13,7 @@ pub struct Tooltip {
|
||||||
|
|
||||||
impl Tooltip {
|
impl Tooltip {
|
||||||
pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
|
pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
|
||||||
cx.build_view(|cx| Self {
|
cx.build_view(|_cx| Self {
|
||||||
title: title.into(),
|
title: title.into(),
|
||||||
meta: None,
|
meta: None,
|
||||||
key_binding: None,
|
key_binding: None,
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub use gpui::{
|
||||||
|
|
||||||
pub use crate::StyledExt;
|
pub use crate::StyledExt;
|
||||||
pub use crate::{ButtonVariant, Color};
|
pub use crate::{ButtonVariant, Color};
|
||||||
pub use theme2::ActiveTheme;
|
pub use theme::ActiveTheme;
|
||||||
|
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
|
||||||
|
@ -16,12 +16,6 @@ pub enum IconSide {
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
|
|
||||||
pub enum OverflowStyle {
|
|
||||||
Hidden,
|
|
||||||
Wrap,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
|
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
|
||||||
pub enum InteractionState {
|
pub enum InteractionState {
|
||||||
/// An element that is enabled and not hovered, active, focused, or disabled.
|
/// An element that is enabled and not hovered, active, focused, or disabled.
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use gpui::{Styled, WindowContext};
|
use gpui::{px, Styled, WindowContext};
|
||||||
use theme2::ActiveTheme;
|
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::{ElevationIndex, UITextSize};
|
use crate::{ElevationIndex, UITextSize};
|
||||||
|
|
||||||
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
||||||
this.bg(cx.theme().colors().elevated_surface_background)
|
this.bg(cx.theme().colors().elevated_surface_background)
|
||||||
.z_index(index.z_index())
|
.z_index(index.z_index())
|
||||||
.rounded_lg()
|
.rounded(px(8.))
|
||||||
.border()
|
.border()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.shadow(index.shadow())
|
.shadow(index.shadow())
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui::{Hsla, WindowContext};
|
use gpui::{Hsla, WindowContext};
|
||||||
use theme2::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
#![doc = include_str!("../docs/hello-world.md")]
|
#![doc = include_str!("../docs/hello-world.md")]
|
||||||
#![doc = include_str!("../docs/building-ui.md")]
|
#![doc = include_str!("../docs/building-ui.md")]
|
||||||
#![doc = include_str!("../docs/todo.md")]
|
#![doc = include_str!("../docs/todo.md")]
|
||||||
// TODO: Fix warnings instead of supressing.
|
|
||||||
#![allow(dead_code, unused_variables)]
|
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
|
@ -16,55 +16,54 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
|
||||||
fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
|
fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
|
||||||
let suffix = if distance < 0 { " from now" } else { " ago" };
|
let suffix = if distance < 0 { " from now" } else { " ago" };
|
||||||
|
|
||||||
let d = distance.abs();
|
let distance = distance.abs();
|
||||||
|
|
||||||
let minutes = d / 60;
|
let minutes = distance / 60;
|
||||||
let hours = d / 3600;
|
let hours = distance / 3_600;
|
||||||
let days = d / 86400;
|
let days = distance / 86_400;
|
||||||
let months = d / 2592000;
|
let months = distance / 2_592_000;
|
||||||
let years = d / 31536000;
|
|
||||||
|
|
||||||
let string = if d < 5 && include_seconds {
|
let string = if distance < 5 && include_seconds {
|
||||||
"less than 5 seconds".to_string()
|
"less than 5 seconds".to_string()
|
||||||
} else if d < 10 && include_seconds {
|
} else if distance < 10 && include_seconds {
|
||||||
"less than 10 seconds".to_string()
|
"less than 10 seconds".to_string()
|
||||||
} else if d < 20 && include_seconds {
|
} else if distance < 20 && include_seconds {
|
||||||
"less than 20 seconds".to_string()
|
"less than 20 seconds".to_string()
|
||||||
} else if d < 40 && include_seconds {
|
} else if distance < 40 && include_seconds {
|
||||||
"half a minute".to_string()
|
"half a minute".to_string()
|
||||||
} else if d < 60 && include_seconds {
|
} else if distance < 60 && include_seconds {
|
||||||
"less than a minute".to_string()
|
"less than a minute".to_string()
|
||||||
} else if d < 90 && include_seconds {
|
} else if distance < 90 && include_seconds {
|
||||||
"1 minute".to_string()
|
"1 minute".to_string()
|
||||||
} else if d < 30 {
|
} else if distance < 30 {
|
||||||
"less than a minute".to_string()
|
"less than a minute".to_string()
|
||||||
} else if d < 90 {
|
} else if distance < 90 {
|
||||||
"1 minute".to_string()
|
"1 minute".to_string()
|
||||||
} else if d < 2700 {
|
} else if distance < 2_700 {
|
||||||
format!("{} minutes", minutes)
|
format!("{} minutes", minutes)
|
||||||
} else if d < 5400 {
|
} else if distance < 5_400 {
|
||||||
"about 1 hour".to_string()
|
"about 1 hour".to_string()
|
||||||
} else if d < 86400 {
|
} else if distance < 86_400 {
|
||||||
format!("about {} hours", hours)
|
format!("about {} hours", hours)
|
||||||
} else if d < 172800 {
|
} else if distance < 172_800 {
|
||||||
"1 day".to_string()
|
"1 day".to_string()
|
||||||
} else if d < 2592000 {
|
} else if distance < 2_592_000 {
|
||||||
format!("{} days", days)
|
format!("{} days", days)
|
||||||
} else if d < 5184000 {
|
} else if distance < 5_184_000 {
|
||||||
"about 1 month".to_string()
|
"about 1 month".to_string()
|
||||||
} else if d < 7776000 {
|
} else if distance < 7_776_000 {
|
||||||
"about 2 months".to_string()
|
"about 2 months".to_string()
|
||||||
} else if d < 31540000 {
|
} else if distance < 31_540_000 {
|
||||||
format!("{} months", months)
|
format!("{} months", months)
|
||||||
} else if d < 39425000 {
|
} else if distance < 39_425_000 {
|
||||||
"about 1 year".to_string()
|
"about 1 year".to_string()
|
||||||
} else if d < 55195000 {
|
} else if distance < 55_195_000 {
|
||||||
"over 1 year".to_string()
|
"over 1 year".to_string()
|
||||||
} else if d < 63080000 {
|
} else if distance < 63_080_000 {
|
||||||
"almost 2 years".to_string()
|
"almost 2 years".to_string()
|
||||||
} else {
|
} else {
|
||||||
let years = d / 31536000;
|
let years = distance / 31_536_000;
|
||||||
let remaining_months = (d % 31536000) / 2592000;
|
let remaining_months = (distance % 31_536_000) / 2_592_000;
|
||||||
|
|
||||||
if remaining_months < 3 {
|
if remaining_months < 3 {
|
||||||
format!("about {} years", years)
|
format!("about {} years", years)
|
||||||
|
@ -76,7 +75,7 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
|
||||||
};
|
};
|
||||||
|
|
||||||
if add_suffix {
|
if add_suffix {
|
||||||
return format!("{}{}", string, suffix);
|
format!("{}{}", string, suffix)
|
||||||
} else {
|
} else {
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ editor = { package = "editor2", path = "../editor2" }
|
||||||
fs = { package = "fs2", path = "../fs2" }
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
install_cli = { package = "install_cli2", path = "../install_cli2" }
|
install_cli = { package = "install_cli2", path = "../install_cli2" }
|
||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use super::base_keymap_setting::BaseKeymap;
|
use super::base_keymap_setting::BaseKeymap;
|
||||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, DismissEvent, EventEmitter, FocusableView, IntoElement, Render, Task,
|
actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
|
||||||
View, ViewContext, VisualContext, WeakView,
|
View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use picker::{simple_picker_match, Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{update_settings_file, Settings};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use ui::ListItem;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{ui::HighlightedLabel, Workspace};
|
use workspace::{ui::HighlightedLabel, Workspace};
|
||||||
|
|
||||||
|
@ -97,6 +98,8 @@ impl BaseKeymapSelectorDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for BaseKeymapSelectorDelegate {
|
impl PickerDelegate for BaseKeymapSelectorDelegate {
|
||||||
|
type ListItem = ui::ListItem;
|
||||||
|
|
||||||
fn placeholder_text(&self) -> Arc<str> {
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
"Select a base keymap...".into()
|
"Select a base keymap...".into()
|
||||||
}
|
}
|
||||||
|
@ -188,13 +191,18 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut gpui::ViewContext<Picker<Self>>,
|
_cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||||
) -> gpui::AnyElement {
|
) -> Option<Self::ListItem> {
|
||||||
let keymap_match = &self.matches[ix];
|
let keymap_match = &self.matches[ix];
|
||||||
|
|
||||||
simple_picker_match(selected, cx, |_cx| {
|
Some(
|
||||||
HighlightedLabel::new(keymap_match.string.clone(), keymap_match.positions.clone())
|
ListItem::new(ix)
|
||||||
.into_any_element()
|
.selected(selected)
|
||||||
})
|
.inset(true)
|
||||||
|
.child(HighlightedLabel::new(
|
||||||
|
keymap_match.string.clone(),
|
||||||
|
keymap_match.positions.clone(),
|
||||||
|
)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,10 +95,6 @@ impl Render for ModalLayer {
|
||||||
.track_focus(&active_modal.focus_handle)
|
.track_focus(&active_modal.focus_handle)
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
// needed to prevent mouse events leaking to the
|
|
||||||
// UI below. // todo! for gpui3.
|
|
||||||
.on_any_mouse_down(|_, cx| cx.stop_propagation())
|
|
||||||
.on_any_mouse_up(|_, cx| cx.stop_propagation())
|
|
||||||
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||||
this.hide_modal(cx);
|
this.hide_modal(cx);
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -2640,12 +2640,11 @@ impl Workspace {
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.justify_end()
|
.justify_end()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.children(self.notifications.iter().map(|(_, _, notification)| {
|
.children(
|
||||||
div()
|
self.notifications
|
||||||
.on_any_mouse_down(|_, cx| cx.stop_propagation())
|
.iter()
|
||||||
.on_any_mouse_up(|_, cx| cx.stop_propagation())
|
.map(|(_, _, notification)| notification.to_any()),
|
||||||
.child(notification.to_any())
|
),
|
||||||
})),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,7 @@ tree-sitter-lua.workspace = true
|
||||||
tree-sitter-nix.workspace = true
|
tree-sitter-nix.workspace = true
|
||||||
tree-sitter-nu.workspace = true
|
tree-sitter-nu.workspace = true
|
||||||
tree-sitter-vue.workspace = true
|
tree-sitter-vue.workspace = true
|
||||||
|
tree-sitter-uiua.workspace = true
|
||||||
|
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
|
|
@ -17,6 +17,7 @@ mod json;
|
||||||
#[cfg(feature = "plugin_runtime")]
|
#[cfg(feature = "plugin_runtime")]
|
||||||
mod language_plugin;
|
mod language_plugin;
|
||||||
mod lua;
|
mod lua;
|
||||||
|
mod nu;
|
||||||
mod php;
|
mod php;
|
||||||
mod python;
|
mod python;
|
||||||
mod ruby;
|
mod ruby;
|
||||||
|
@ -24,6 +25,7 @@ mod rust;
|
||||||
mod svelte;
|
mod svelte;
|
||||||
mod tailwind;
|
mod tailwind;
|
||||||
mod typescript;
|
mod typescript;
|
||||||
|
mod uiua;
|
||||||
mod vue;
|
mod vue;
|
||||||
mod yaml;
|
mod yaml;
|
||||||
|
|
||||||
|
@ -210,12 +212,21 @@ pub fn init(
|
||||||
language("elm", tree_sitter_elm::language(), vec![]);
|
language("elm", tree_sitter_elm::language(), vec![]);
|
||||||
language("glsl", tree_sitter_glsl::language(), vec![]);
|
language("glsl", tree_sitter_glsl::language(), vec![]);
|
||||||
language("nix", tree_sitter_nix::language(), vec![]);
|
language("nix", tree_sitter_nix::language(), vec![]);
|
||||||
language("nu", tree_sitter_nu::language(), vec![]);
|
language(
|
||||||
|
"nu",
|
||||||
|
tree_sitter_nu::language(),
|
||||||
|
vec![Arc::new(nu::NuLanguageServer {})],
|
||||||
|
);
|
||||||
language(
|
language(
|
||||||
"vue",
|
"vue",
|
||||||
tree_sitter_vue::language(),
|
tree_sitter_vue::language(),
|
||||||
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
||||||
);
|
);
|
||||||
|
language(
|
||||||
|
"uiua",
|
||||||
|
tree_sitter_uiua::language(),
|
||||||
|
vec![Arc::new(uiua::UiuaLanguageServer {})],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
|
81
crates/zed/src/languages/nu.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use language::{CodeLabel, Language, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
pub struct NuLanguageServer;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for NuLanguageServer {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("nu".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"nu"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
_version: Box<dyn 'static + Send + Any>,
|
||||||
|
_container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
Err(anyhow!(
|
||||||
|
"nu v0.87.0 or greater must be installed and available in your $PATH"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
_: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: "nu".into(),
|
||||||
|
arguments: vec!["--lsp".into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_be_reinstalled(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
completion: &lsp::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
return Some(CodeLabel {
|
||||||
|
runs: language
|
||||||
|
.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
|
||||||
|
text: completion.label.clone(),
|
||||||
|
filter_range: 0..completion.label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_symbol(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
_: lsp::SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
Some(CodeLabel {
|
||||||
|
runs: language.highlight_text(&name.into(), 0..name.len()),
|
||||||
|
text: name.to_string(),
|
||||||
|
filter_range: 0..name.len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
55
crates/zed/src/languages/uiua.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use std::{any::Any, path::PathBuf};
|
||||||
|
|
||||||
|
pub struct UiuaLanguageServer;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for UiuaLanguageServer {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("uiua".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"uiua"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
_version: Box<dyn 'static + Send + Any>,
|
||||||
|
_container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
Err(anyhow!(
|
||||||
|
"uiua must be installed and available in your $PATH"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
_: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: "uiua".into(),
|
||||||
|
arguments: vec!["lsp".into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_be_reinstalled(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
10
crates/zed/src/languages/uiua/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
name = "Uiua"
|
||||||
|
path_suffixes = ["ua"]
|
||||||
|
line_comment = "# "
|
||||||
|
autoclose_before = ")]}\""
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = false },
|
||||||
|
{ start = "[", end = "]", close = true, newline = false },
|
||||||
|
{ start = "(", end = ")", close = true, newline = false },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
]
|
50
crates/zed/src/languages/uiua/highlights.scm
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
(openParen)
|
||||||
|
(closeParen)
|
||||||
|
(openCurly)
|
||||||
|
(closeCurly)
|
||||||
|
(openBracket)
|
||||||
|
(closeBracket)
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
(branchSeparator)
|
||||||
|
(underscore)
|
||||||
|
] @constructor
|
||||||
|
; ] @punctuation.delimiter
|
||||||
|
|
||||||
|
[ (character) ] @constant.character
|
||||||
|
[ (comment) ] @comment
|
||||||
|
[ (constant) ] @constant.numeric
|
||||||
|
[ (identifier) ] @variable
|
||||||
|
[ (leftArrow) ] @keyword
|
||||||
|
[ (function) ] @function
|
||||||
|
[ (modifier1) ] @operator
|
||||||
|
[ (modifier2) ] @operator
|
||||||
|
[ (number) ] @constant.numeric
|
||||||
|
[ (placeHolder) ] @special
|
||||||
|
[ (otherConstant) ] @string.special
|
||||||
|
[ (signature) ] @type
|
||||||
|
[ (system) ] @function.builtin
|
||||||
|
[ (tripleMinus) ] @module
|
||||||
|
|
||||||
|
; planet
|
||||||
|
[
|
||||||
|
"id"
|
||||||
|
"identity"
|
||||||
|
"∘"
|
||||||
|
"dip"
|
||||||
|
"⊙"
|
||||||
|
"gap"
|
||||||
|
"⋅"
|
||||||
|
] @tag
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(multiLineString)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
; [
|
||||||
|
; (deprecated)
|
||||||
|
; (identifierDeprecated)
|
||||||
|
; ] @warning
|
3
crates/zed/src/languages/uiua/indents.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[
|
||||||
|
(array)
|
||||||
|
] @indent
|
|
@ -21,7 +21,7 @@ audio = { package = "audio2", path = "../audio2" }
|
||||||
auto_update = { package = "auto_update2", path = "../auto_update2" }
|
auto_update = { package = "auto_update2", path = "../auto_update2" }
|
||||||
# breadcrumbs = { path = "../breadcrumbs" }
|
# breadcrumbs = { path = "../breadcrumbs" }
|
||||||
call = { package = "call2", path = "../call2" }
|
call = { package = "call2", path = "../call2" }
|
||||||
# channel = { path = "../channel" }
|
channel = { package = "channel2", path = "../channel2" }
|
||||||
cli = { path = "../cli" }
|
cli = { path = "../cli" }
|
||||||
collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
|
collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
|
@ -136,6 +136,7 @@ tree-sitter-lua.workspace = true
|
||||||
tree-sitter-nix.workspace = true
|
tree-sitter-nix.workspace = true
|
||||||
tree-sitter-nu.workspace = true
|
tree-sitter-nu.workspace = true
|
||||||
tree-sitter-vue.workspace = true
|
tree-sitter-vue.workspace = true
|
||||||
|
tree-sitter-uiua.workspace = true
|
||||||
|
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
|
|
@ -18,6 +18,7 @@ mod json;
|
||||||
#[cfg(feature = "plugin_runtime")]
|
#[cfg(feature = "plugin_runtime")]
|
||||||
mod language_plugin;
|
mod language_plugin;
|
||||||
mod lua;
|
mod lua;
|
||||||
|
mod nu;
|
||||||
mod php;
|
mod php;
|
||||||
mod python;
|
mod python;
|
||||||
mod ruby;
|
mod ruby;
|
||||||
|
@ -25,6 +26,7 @@ mod rust;
|
||||||
mod svelte;
|
mod svelte;
|
||||||
mod tailwind;
|
mod tailwind;
|
||||||
mod typescript;
|
mod typescript;
|
||||||
|
mod uiua;
|
||||||
mod vue;
|
mod vue;
|
||||||
mod yaml;
|
mod yaml;
|
||||||
|
|
||||||
|
@ -211,12 +213,21 @@ pub fn init(
|
||||||
language("elm", tree_sitter_elm::language(), vec![]);
|
language("elm", tree_sitter_elm::language(), vec![]);
|
||||||
language("glsl", tree_sitter_glsl::language(), vec![]);
|
language("glsl", tree_sitter_glsl::language(), vec![]);
|
||||||
language("nix", tree_sitter_nix::language(), vec![]);
|
language("nix", tree_sitter_nix::language(), vec![]);
|
||||||
language("nu", tree_sitter_nu::language(), vec![]);
|
language(
|
||||||
|
"nu",
|
||||||
|
tree_sitter_nu::language(),
|
||||||
|
vec![Arc::new(nu::NuLanguageServer {})],
|
||||||
|
);
|
||||||
language(
|
language(
|
||||||
"vue",
|
"vue",
|
||||||
tree_sitter_vue::language(),
|
tree_sitter_vue::language(),
|
||||||
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
||||||
);
|
);
|
||||||
|
language(
|
||||||
|
"uiua",
|
||||||
|
tree_sitter_uiua::language(),
|
||||||
|
vec![Arc::new(uiua::UiuaLanguageServer {})],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
|
55
crates/zed2/src/languages/nu.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use std::{any::Any, path::PathBuf};
|
||||||
|
|
||||||
|
pub struct NuLanguageServer;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for NuLanguageServer {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("nu".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"nu"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
_version: Box<dyn 'static + Send + Any>,
|
||||||
|
_container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
Err(anyhow!(
|
||||||
|
"nu v0.87.0 or greater must be installed and available in your $PATH"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
_: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: "nu".into(),
|
||||||
|
arguments: vec!["--lsp".into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_be_reinstalled(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
55
crates/zed2/src/languages/uiua.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use std::{any::Any, path::PathBuf};
|
||||||
|
|
||||||
|
pub struct UiuaLanguageServer;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for UiuaLanguageServer {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("uiua".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"uiua"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
_version: Box<dyn 'static + Send + Any>,
|
||||||
|
_container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
Err(anyhow!(
|
||||||
|
"uiua must be installed and available in your $PATH"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
_: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: "uiua".into(),
|
||||||
|
arguments: vec!["lsp".into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_be_reinstalled(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
10
crates/zed2/src/languages/uiua/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
name = "Uiua"
|
||||||
|
path_suffixes = ["ua"]
|
||||||
|
line_comment = "# "
|
||||||
|
autoclose_before = ")]}\""
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = false},
|
||||||
|
{ start = "[", end = "]", close = true, newline = false },
|
||||||
|
{ start = "(", end = ")", close = true, newline = false },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
]
|
50
crates/zed2/src/languages/uiua/highlights.scm
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
(openParen)
|
||||||
|
(closeParen)
|
||||||
|
(openCurly)
|
||||||
|
(closeCurly)
|
||||||
|
(openBracket)
|
||||||
|
(closeBracket)
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
(branchSeparator)
|
||||||
|
(underscore)
|
||||||
|
] @constructor
|
||||||
|
; ] @punctuation.delimiter
|
||||||
|
|
||||||
|
[ (character) ] @constant.character
|
||||||
|
[ (comment) ] @comment
|
||||||
|
[ (constant) ] @constant.numeric
|
||||||
|
[ (identifier) ] @variable
|
||||||
|
[ (leftArrow) ] @keyword
|
||||||
|
[ (function) ] @function
|
||||||
|
[ (modifier1) ] @operator
|
||||||
|
[ (modifier2) ] @operator
|
||||||
|
[ (number) ] @constant.numeric
|
||||||
|
[ (placeHolder) ] @special
|
||||||
|
[ (otherConstant) ] @string.special
|
||||||
|
[ (signature) ] @type
|
||||||
|
[ (system) ] @function.builtin
|
||||||
|
[ (tripleMinus) ] @module
|
||||||
|
|
||||||
|
; planet
|
||||||
|
[
|
||||||
|
"id"
|
||||||
|
"identity"
|
||||||
|
"∘"
|
||||||
|
"dip"
|
||||||
|
"⊙"
|
||||||
|
"gap"
|
||||||
|
"⋅"
|
||||||
|
] @tag
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(multiLineString)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
; [
|
||||||
|
; (deprecated)
|
||||||
|
; (identifierDeprecated)
|
||||||
|
; ] @warning
|
3
crates/zed2/src/languages/uiua/indents.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[
|
||||||
|
(array)
|
||||||
|
] @indent
|
|
@ -188,7 +188,7 @@ fn main() {
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages,
|
languages,
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
user_store,
|
user_store: user_store.clone(),
|
||||||
fs,
|
fs,
|
||||||
build_window_options,
|
build_window_options,
|
||||||
call_factory: call::Call::new,
|
call_factory: call::Call::new,
|
||||||
|
@ -208,7 +208,7 @@ fn main() {
|
||||||
// outline::init(cx);
|
// outline::init(cx);
|
||||||
// project_symbols::init(cx);
|
// project_symbols::init(cx);
|
||||||
project_panel::init(Assets, cx);
|
project_panel::init(Assets, cx);
|
||||||
// channel::init(&client, user_store.clone(), cx);
|
channel::init(&client, user_store.clone(), cx);
|
||||||
// diagnostics::init(cx);
|
// diagnostics::init(cx);
|
||||||
search::init(cx);
|
search::init(cx);
|
||||||
// semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
|
// semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
|
||||||
|
|