mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 19:08:00 +03:00
Remove 2 suffix from gpui_macros, fix compile errors in tests
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
f5ba22659b
commit
83f4c61657
@ -1,6 +1,3 @@
|
|||||||
[alias]
|
|
||||||
xtask = "run --package xtask --"
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
# v0 mangling scheme provides more detailed backtraces around closures
|
# v0 mangling scheme provides more detailed backtraces around closures
|
||||||
rustflags = ["-C", "symbol-mangling-version=v0"]
|
rustflags = ["-C", "symbol-mangling-version=v0"]
|
||||||
|
56
Cargo.lock
generated
56
Cargo.lock
generated
@ -1618,19 +1618,6 @@ dependencies = [
|
|||||||
"zed_actions",
|
"zed_actions",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "component_test"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"gpui",
|
|
||||||
"project",
|
|
||||||
"settings",
|
|
||||||
"theme",
|
|
||||||
"util",
|
|
||||||
"workspace",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@ -1665,17 +1652,6 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "context_menu"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"gpui",
|
|
||||||
"menu",
|
|
||||||
"settings",
|
|
||||||
"smallvec",
|
|
||||||
"theme",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -2354,14 +2330,6 @@ version = "0.15.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "drag_and_drop"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"collections",
|
|
||||||
"gpui",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dwrote"
|
name = "dwrote"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -3185,7 +3153,7 @@ dependencies = [
|
|||||||
"font-kit",
|
"font-kit",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
"gpui2_macros",
|
"gpui_macros",
|
||||||
"image",
|
"image",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@ -3225,20 +3193,10 @@ dependencies = [
|
|||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gpui2_macros"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
@ -7677,6 +7635,7 @@ dependencies = [
|
|||||||
"story",
|
"story",
|
||||||
"strum",
|
"strum",
|
||||||
"theme",
|
"theme",
|
||||||
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -9936,17 +9895,6 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xtask"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"clap 4.4.4",
|
|
||||||
"schemars",
|
|
||||||
"serde_json",
|
|
||||||
"theme",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -15,15 +15,12 @@ members = [
|
|||||||
"crates/collab_ui",
|
"crates/collab_ui",
|
||||||
"crates/collections",
|
"crates/collections",
|
||||||
"crates/command_palette",
|
"crates/command_palette",
|
||||||
"crates/component_test",
|
|
||||||
"crates/context_menu",
|
|
||||||
"crates/copilot",
|
"crates/copilot",
|
||||||
"crates/copilot_button",
|
"crates/copilot_button",
|
||||||
"crates/db",
|
"crates/db",
|
||||||
"crates/refineable",
|
"crates/refineable",
|
||||||
"crates/refineable/derive_refineable",
|
"crates/refineable/derive_refineable",
|
||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
"crates/drag_and_drop",
|
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
"crates/feature_flags",
|
"crates/feature_flags",
|
||||||
"crates/feedback",
|
"crates/feedback",
|
||||||
@ -36,7 +33,7 @@ members = [
|
|||||||
"crates/gpui",
|
"crates/gpui",
|
||||||
"crates/gpui_macros",
|
"crates/gpui_macros",
|
||||||
"crates/gpui",
|
"crates/gpui",
|
||||||
"crates/gpui2_macros",
|
"crates/gpui_macros",
|
||||||
"crates/install_cli",
|
"crates/install_cli",
|
||||||
"crates/journal",
|
"crates/journal",
|
||||||
"crates/journal",
|
"crates/journal",
|
||||||
@ -85,7 +82,6 @@ members = [
|
|||||||
"crates/vcs_menu",
|
"crates/vcs_menu",
|
||||||
"crates/workspace",
|
"crates/workspace",
|
||||||
"crates/welcome",
|
"crates/welcome",
|
||||||
"crates/xtask",
|
|
||||||
"crates/zed",
|
"crates/zed",
|
||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyElement, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
|
div, AnyElement, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "component_test"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/component_test.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow.workspace = true
|
|
||||||
gpui = { path = "../gpui" }
|
|
||||||
settings = { path = "../settings" }
|
|
||||||
util = { path = "../util" }
|
|
||||||
theme = { path = "../theme" }
|
|
||||||
workspace = { path = "../workspace" }
|
|
||||||
project = { path = "../project" }
|
|
@ -1,121 +0,0 @@
|
|||||||
use gpui::{
|
|
||||||
actions,
|
|
||||||
elements::{Component, Flex, ParentElement, SafeStylable},
|
|
||||||
AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
|
||||||
};
|
|
||||||
use project::Project;
|
|
||||||
use theme::components::{action_button::Button, label::Label, ComponentExt};
|
|
||||||
use workspace::{
|
|
||||||
item::Item, register_deserializable_item, ItemId, Pane, PaneBackdrop, Workspace, WorkspaceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
cx.add_action(ComponentTest::toggle_disclosure);
|
|
||||||
cx.add_action(ComponentTest::toggle_toggle);
|
|
||||||
cx.add_action(ComponentTest::deploy);
|
|
||||||
register_deserializable_item::<ComponentTest>(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
actions!(
|
|
||||||
test,
|
|
||||||
[NoAction, ToggleDisclosure, ToggleToggle, NewComponentTest]
|
|
||||||
);
|
|
||||||
|
|
||||||
struct ComponentTest {
|
|
||||||
disclosed: bool,
|
|
||||||
toggled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentTest {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
disclosed: false,
|
|
||||||
toggled: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deploy(workspace: &mut Workspace, _: &NewComponentTest, cx: &mut ViewContext<Workspace>) {
|
|
||||||
workspace.add_item(Box::new(cx.add_view(|_| ComponentTest::new())), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_disclosure(&mut self, _: &ToggleDisclosure, cx: &mut ViewContext<Self>) {
|
|
||||||
self.disclosed = !self.disclosed;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_toggle(&mut self, _: &ToggleToggle, cx: &mut ViewContext<Self>) {
|
|
||||||
self.toggled = !self.toggled;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entity for ComponentTest {
|
|
||||||
type Event = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ComponentTest {
|
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"Component Test"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
|
||||||
let theme = theme::current(cx);
|
|
||||||
|
|
||||||
PaneBackdrop::new(
|
|
||||||
cx.view_id(),
|
|
||||||
Flex::column()
|
|
||||||
.with_spacing(10.)
|
|
||||||
.with_child(
|
|
||||||
Button::action(NoAction)
|
|
||||||
.with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
|
|
||||||
.with_contents(Label::new("Click me!"))
|
|
||||||
.with_style(theme.component_test.button.clone())
|
|
||||||
.element(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Button::action(ToggleToggle)
|
|
||||||
.with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
|
|
||||||
.with_contents(Label::new("Toggle me!"))
|
|
||||||
.toggleable(self.toggled)
|
|
||||||
.with_style(theme.component_test.toggle.clone())
|
|
||||||
.element(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Label::new("A disclosure")
|
|
||||||
.disclosable(Some(self.disclosed), Box::new(ToggleDisclosure))
|
|
||||||
.with_style(theme.component_test.disclosure.clone())
|
|
||||||
.element(),
|
|
||||||
)
|
|
||||||
.constrained()
|
|
||||||
.with_width(200.)
|
|
||||||
.aligned()
|
|
||||||
.into_any(),
|
|
||||||
)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Item for ComponentTest {
|
|
||||||
fn tab_content<V: 'static>(
|
|
||||||
&self,
|
|
||||||
_: Option<usize>,
|
|
||||||
style: &theme::Tab,
|
|
||||||
_: &AppContext,
|
|
||||||
) -> gpui::AnyElement<V> {
|
|
||||||
gpui::elements::Label::new("Component test", style.label.clone()).into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialized_item_kind() -> Option<&'static str> {
|
|
||||||
Some("ComponentTest")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize(
|
|
||||||
_project: ModelHandle<Project>,
|
|
||||||
_workspace: WeakViewHandle<Workspace>,
|
|
||||||
_workspace_id: WorkspaceId,
|
|
||||||
_item_id: ItemId,
|
|
||||||
cx: &mut ViewContext<Pane>,
|
|
||||||
) -> Task<anyhow::Result<ViewHandle<Self>>> {
|
|
||||||
Task::ready(Ok(cx.add_view(|_| Self::new())))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "context_menu"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/context_menu.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
gpui = { path = "../gpui" }
|
|
||||||
menu = { path = "../menu" }
|
|
||||||
settings = { path = "../settings" }
|
|
||||||
theme = { path = "../theme" }
|
|
||||||
smallvec.workspace = true
|
|
@ -1,528 +0,0 @@
|
|||||||
use gpui::{
|
|
||||||
anyhow::{self, anyhow},
|
|
||||||
elements::*,
|
|
||||||
geometry::vector::Vector2F,
|
|
||||||
keymap_matcher::KeymapContext,
|
|
||||||
platform::{CursorStyle, MouseButton},
|
|
||||||
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
|
|
||||||
View, ViewContext,
|
|
||||||
};
|
|
||||||
use menu::*;
|
|
||||||
use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
cx.add_action(ContextMenu::select_first);
|
|
||||||
cx.add_action(ContextMenu::select_last);
|
|
||||||
cx.add_action(ContextMenu::select_next);
|
|
||||||
cx.add_action(ContextMenu::select_prev);
|
|
||||||
cx.add_action(ContextMenu::confirm);
|
|
||||||
cx.add_action(ContextMenu::cancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type StaticItem = Box<dyn Fn(&mut AppContext) -> AnyElement<ContextMenu>>;
|
|
||||||
|
|
||||||
type ContextMenuItemBuilder =
|
|
||||||
Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>>;
|
|
||||||
|
|
||||||
pub enum ContextMenuItemLabel {
|
|
||||||
String(Cow<'static, str>),
|
|
||||||
Element(ContextMenuItemBuilder),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Cow<'static, str>> for ContextMenuItemLabel {
|
|
||||||
fn from(s: Cow<'static, str>) -> Self {
|
|
||||||
Self::String(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&'static str> for ContextMenuItemLabel {
|
|
||||||
fn from(s: &'static str) -> Self {
|
|
||||||
Self::String(s.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for ContextMenuItemLabel {
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
Self::String(s.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for ContextMenuItemLabel
|
|
||||||
where
|
|
||||||
T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>,
|
|
||||||
{
|
|
||||||
fn from(f: T) -> Self {
|
|
||||||
Self::Element(Box::new(f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ContextMenuItemAction {
|
|
||||||
Action(Box<dyn Action>),
|
|
||||||
Handler(Arc<dyn Fn(&mut ViewContext<ContextMenu>)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for ContextMenuItemAction {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Action(action) => Self::Action(action.boxed_clone()),
|
|
||||||
Self::Handler(handler) => Self::Handler(handler.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ContextMenuItem {
|
|
||||||
Item {
|
|
||||||
label: ContextMenuItemLabel,
|
|
||||||
action: ContextMenuItemAction,
|
|
||||||
},
|
|
||||||
Static(StaticItem),
|
|
||||||
Separator,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextMenuItem {
|
|
||||||
pub fn action(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
|
|
||||||
Self::Item {
|
|
||||||
label: label.into(),
|
|
||||||
action: ContextMenuItemAction::Action(Box::new(action)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handler(
|
|
||||||
label: impl Into<ContextMenuItemLabel>,
|
|
||||||
handler: impl 'static + Fn(&mut ViewContext<ContextMenu>),
|
|
||||||
) -> Self {
|
|
||||||
Self::Item {
|
|
||||||
label: label.into(),
|
|
||||||
action: ContextMenuItemAction::Handler(Arc::new(handler)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn separator() -> Self {
|
|
||||||
Self::Separator
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_action(&self) -> bool {
|
|
||||||
matches!(self, Self::Item { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_id(&self) -> Option<TypeId> {
|
|
||||||
match self {
|
|
||||||
ContextMenuItem::Item { action, .. } => match action {
|
|
||||||
ContextMenuItemAction::Action(action) => Some(action.id()),
|
|
||||||
ContextMenuItemAction::Handler(_) => None,
|
|
||||||
},
|
|
||||||
ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContextMenu {
|
|
||||||
show_count: usize,
|
|
||||||
anchor_position: Vector2F,
|
|
||||||
anchor_corner: AnchorCorner,
|
|
||||||
position_mode: OverlayPositionMode,
|
|
||||||
items: Vec<ContextMenuItem>,
|
|
||||||
selected_index: Option<usize>,
|
|
||||||
visible: bool,
|
|
||||||
delay_cancel: bool,
|
|
||||||
previously_focused_view_id: Option<usize>,
|
|
||||||
parent_view_id: usize,
|
|
||||||
_actions_observation: Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entity for ContextMenu {
|
|
||||||
type Event = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ContextMenu {
|
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"ContextMenu"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
|
||||||
Self::reset_to_default_keymap_context(keymap);
|
|
||||||
keymap.add_identifier("menu");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
|
||||||
if !self.visible {
|
|
||||||
return Empty::new().into_any();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the menu once at minimum width.
|
|
||||||
let mut collapsed_menu = self.render_menu_for_measurement(cx);
|
|
||||||
let expanded_menu =
|
|
||||||
self.render_menu(cx)
|
|
||||||
.constrained()
|
|
||||||
.dynamically(move |constraint, view, cx| {
|
|
||||||
SizeConstraint::strict_along(
|
|
||||||
Axis::Horizontal,
|
|
||||||
collapsed_menu.layout(constraint, view, cx).0.x(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
Overlay::new(expanded_menu)
|
|
||||||
.with_hoverable(true)
|
|
||||||
.with_fit_mode(OverlayFitMode::SnapToWindow)
|
|
||||||
.with_anchor_position(self.anchor_position)
|
|
||||||
.with_anchor_corner(self.anchor_corner)
|
|
||||||
.with_position_mode(self.position_mode)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
|
||||||
self.reset(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextMenu {
|
|
||||||
pub fn new(parent_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
show_count: 0,
|
|
||||||
delay_cancel: false,
|
|
||||||
anchor_position: Default::default(),
|
|
||||||
anchor_corner: AnchorCorner::TopLeft,
|
|
||||||
position_mode: OverlayPositionMode::Window,
|
|
||||||
items: Default::default(),
|
|
||||||
selected_index: Default::default(),
|
|
||||||
visible: Default::default(),
|
|
||||||
previously_focused_view_id: Default::default(),
|
|
||||||
parent_view_id,
|
|
||||||
_actions_observation: cx.observe_actions(Self::action_dispatched),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visible(&self) -> bool {
|
|
||||||
self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(ix) = self
|
|
||||||
.items
|
|
||||||
.iter()
|
|
||||||
.position(|item| item.action_id() == Some(action_id))
|
|
||||||
{
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
cx.background().timer(Duration::from_millis(50)).await;
|
|
||||||
this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(ix) = self.selected_index {
|
|
||||||
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
|
|
||||||
match action {
|
|
||||||
ContextMenuItemAction::Action(action) => {
|
|
||||||
let window = cx.window();
|
|
||||||
let view_id = self.parent_view_id;
|
|
||||||
let action = action.boxed_clone();
|
|
||||||
cx.app_context()
|
|
||||||
.spawn(|mut cx| async move {
|
|
||||||
window
|
|
||||||
.dispatch_action(view_id, action.as_ref(), &mut cx)
|
|
||||||
.ok_or_else(|| anyhow!("window was closed"))
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
ContextMenuItemAction::Handler(handler) => handler(cx),
|
|
||||||
}
|
|
||||||
self.reset(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delay_cancel(&mut self) {
|
|
||||||
self.delay_cancel = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
|
||||||
if !self.delay_cancel {
|
|
||||||
self.reset(cx);
|
|
||||||
let show_count = self.show_count;
|
|
||||||
cx.defer(move |this, cx| {
|
|
||||||
if cx.handle().is_focused(cx) && this.show_count == show_count {
|
|
||||||
(**cx).focus(this.previously_focused_view_id.take());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.delay_cancel = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
self.items.clear();
|
|
||||||
self.visible = false;
|
|
||||||
self.selected_index.take();
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
|
|
||||||
self.selected_index = self.items.iter().position(|item| item.is_action());
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
|
|
||||||
for (ix, item) in self.items.iter().enumerate().rev() {
|
|
||||||
if item.is_action() {
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(ix) = self.selected_index {
|
|
||||||
for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
|
|
||||||
if item.is_action() {
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.select_first(&Default::default(), cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(ix) = self.selected_index {
|
|
||||||
for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
|
|
||||||
if item.is_action() {
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.select_last(&Default::default(), cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle(
|
|
||||||
&mut self,
|
|
||||||
anchor_position: Vector2F,
|
|
||||||
anchor_corner: AnchorCorner,
|
|
||||||
items: Vec<ContextMenuItem>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
if self.visible() {
|
|
||||||
self.cancel(&Cancel, cx);
|
|
||||||
} else {
|
|
||||||
let mut items = items.into_iter().peekable();
|
|
||||||
if items.peek().is_some() {
|
|
||||||
self.items = items.collect();
|
|
||||||
self.anchor_position = anchor_position;
|
|
||||||
self.anchor_corner = anchor_corner;
|
|
||||||
self.visible = true;
|
|
||||||
self.show_count += 1;
|
|
||||||
if !cx.is_self_focused() {
|
|
||||||
self.previously_focused_view_id = cx.focused_view_id();
|
|
||||||
}
|
|
||||||
cx.focus_self();
|
|
||||||
} else {
|
|
||||||
self.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show(
|
|
||||||
&mut self,
|
|
||||||
anchor_position: Vector2F,
|
|
||||||
anchor_corner: AnchorCorner,
|
|
||||||
items: Vec<ContextMenuItem>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
let mut items = items.into_iter().peekable();
|
|
||||||
if items.peek().is_some() {
|
|
||||||
self.items = items.collect();
|
|
||||||
self.anchor_position = anchor_position;
|
|
||||||
self.anchor_corner = anchor_corner;
|
|
||||||
self.visible = true;
|
|
||||||
self.show_count += 1;
|
|
||||||
if !cx.is_self_focused() {
|
|
||||||
self.previously_focused_view_id = cx.focused_view_id();
|
|
||||||
}
|
|
||||||
cx.focus_self();
|
|
||||||
} else {
|
|
||||||
self.visible = false;
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_position_mode(&mut self, mode: OverlayPositionMode) {
|
|
||||||
self.position_mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_menu_for_measurement(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
|
|
||||||
let style = theme::current(cx).context_menu.clone();
|
|
||||||
Flex::row()
|
|
||||||
.with_child(
|
|
||||||
Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
|
||||||
match item {
|
|
||||||
ContextMenuItem::Item { label, .. } => {
|
|
||||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
|
||||||
let style = style.style_for(&mut Default::default());
|
|
||||||
|
|
||||||
match label {
|
|
||||||
ContextMenuItemLabel::String(label) => {
|
|
||||||
Label::new(label.to_string(), style.label.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
ContextMenuItemLabel::Element(element) => {
|
|
||||||
element(&mut Default::default(), style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenuItem::Static(f) => f(cx),
|
|
||||||
|
|
||||||
ContextMenuItem::Separator => Empty::new()
|
|
||||||
.collapsed()
|
|
||||||
.contained()
|
|
||||||
.with_style(style.separator)
|
|
||||||
.constrained()
|
|
||||||
.with_height(1.)
|
|
||||||
.into_any(),
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Flex::column()
|
|
||||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
|
||||||
match item {
|
|
||||||
ContextMenuItem::Item { action, .. } => {
|
|
||||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
|
||||||
let style = style.style_for(&mut Default::default());
|
|
||||||
|
|
||||||
match action {
|
|
||||||
ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
|
|
||||||
self.parent_view_id,
|
|
||||||
action.boxed_clone(),
|
|
||||||
style.keystroke.container,
|
|
||||||
style.keystroke.text.clone(),
|
|
||||||
)
|
|
||||||
.into_any(),
|
|
||||||
ContextMenuItemAction::Handler(_) => Empty::new().into_any(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenuItem::Static(_) => Empty::new().into_any(),
|
|
||||||
|
|
||||||
ContextMenuItem::Separator => Empty::new()
|
|
||||||
.collapsed()
|
|
||||||
.constrained()
|
|
||||||
.with_height(1.)
|
|
||||||
.contained()
|
|
||||||
.with_style(style.separator)
|
|
||||||
.into_any(),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.contained()
|
|
||||||
.with_margin_left(style.keystroke_margin),
|
|
||||||
)
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_menu(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
|
|
||||||
enum Menu {}
|
|
||||||
enum MenuItem {}
|
|
||||||
|
|
||||||
let style = theme::current(cx).context_menu.clone();
|
|
||||||
|
|
||||||
MouseEventHandler::new::<Menu, _>(0, cx, |_, cx| {
|
|
||||||
Flex::column()
|
|
||||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
|
||||||
match item {
|
|
||||||
ContextMenuItem::Item { label, action } => {
|
|
||||||
let action = action.clone();
|
|
||||||
let view_id = self.parent_view_id;
|
|
||||||
MouseEventHandler::new::<MenuItem, _>(ix, cx, |state, _| {
|
|
||||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
|
||||||
let style = style.style_for(state);
|
|
||||||
let keystroke = match &action {
|
|
||||||
ContextMenuItemAction::Action(action) => Some(
|
|
||||||
KeystrokeLabel::new(
|
|
||||||
view_id,
|
|
||||||
action.boxed_clone(),
|
|
||||||
style.keystroke.container,
|
|
||||||
style.keystroke.text.clone(),
|
|
||||||
)
|
|
||||||
.flex_float(),
|
|
||||||
),
|
|
||||||
ContextMenuItemAction::Handler(_) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Flex::row()
|
|
||||||
.with_child(match label {
|
|
||||||
ContextMenuItemLabel::String(label) => {
|
|
||||||
Label::new(label.clone(), style.label.clone())
|
|
||||||
.contained()
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
ContextMenuItemLabel::Element(element) => {
|
|
||||||
element(state, style)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_children(keystroke)
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container)
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
|
|
||||||
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
|
|
||||||
.on_click(MouseButton::Left, move |_, menu, cx| {
|
|
||||||
menu.cancel(&Default::default(), cx);
|
|
||||||
let window = cx.window();
|
|
||||||
match &action {
|
|
||||||
ContextMenuItemAction::Action(action) => {
|
|
||||||
let action = action.boxed_clone();
|
|
||||||
cx.app_context()
|
|
||||||
.spawn(|mut cx| async move {
|
|
||||||
window
|
|
||||||
.dispatch_action(
|
|
||||||
view_id,
|
|
||||||
action.as_ref(),
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.ok_or_else(|| anyhow!("window was closed"))
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
ContextMenuItemAction::Handler(handler) => handler(cx),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_drag(MouseButton::Left, |_, _, _| {})
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenuItem::Static(f) => f(cx),
|
|
||||||
|
|
||||||
ContextMenuItem::Separator => Empty::new()
|
|
||||||
.constrained()
|
|
||||||
.with_height(1.)
|
|
||||||
.contained()
|
|
||||||
.with_style(style.separator)
|
|
||||||
.into_any(),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container)
|
|
||||||
})
|
|
||||||
.on_down_out(MouseButton::Left, |_, this, cx| {
|
|
||||||
this.cancel(&Default::default(), cx);
|
|
||||||
})
|
|
||||||
.on_down_out(MouseButton::Right, |_, this, cx| {
|
|
||||||
this.cancel(&Default::default(), cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "drag_and_drop"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/drag_and_drop.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
collections = { path = "../collections" }
|
|
||||||
gpui = { path = "../gpui" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
|
@ -1,378 +0,0 @@
|
|||||||
use std::{any::Any, rc::Rc};
|
|
||||||
|
|
||||||
use collections::HashSet;
|
|
||||||
use gpui::{
|
|
||||||
elements::{Empty, MouseEventHandler, Overlay},
|
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
|
||||||
platform::{CursorStyle, Modifiers, MouseButton},
|
|
||||||
scene::{MouseDown, MouseDrag},
|
|
||||||
AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEAD_ZONE: f32 = 4.;
|
|
||||||
|
|
||||||
enum State<V> {
|
|
||||||
Down {
|
|
||||||
region_offset: Vector2F,
|
|
||||||
region: RectF,
|
|
||||||
},
|
|
||||||
DeadZone {
|
|
||||||
region_offset: Vector2F,
|
|
||||||
region: RectF,
|
|
||||||
},
|
|
||||||
Dragging {
|
|
||||||
modifiers: Modifiers,
|
|
||||||
window: AnyWindowHandle,
|
|
||||||
position: Vector2F,
|
|
||||||
region_offset: Vector2F,
|
|
||||||
region: RectF,
|
|
||||||
payload: Rc<dyn Any + 'static>,
|
|
||||||
render: Rc<dyn Fn(&Modifiers, Rc<dyn Any>, &mut ViewContext<V>) -> AnyElement<V>>,
|
|
||||||
},
|
|
||||||
Canceled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V> Clone for State<V> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
&State::Down {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
} => State::Down {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
},
|
|
||||||
&State::DeadZone {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
} => State::DeadZone {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
},
|
|
||||||
State::Dragging {
|
|
||||||
modifiers,
|
|
||||||
window,
|
|
||||||
position,
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
payload,
|
|
||||||
render,
|
|
||||||
} => Self::Dragging {
|
|
||||||
window: window.clone(),
|
|
||||||
position: position.clone(),
|
|
||||||
region_offset: region_offset.clone(),
|
|
||||||
region: region.clone(),
|
|
||||||
payload: payload.clone(),
|
|
||||||
render: render.clone(),
|
|
||||||
modifiers: modifiers.clone(),
|
|
||||||
},
|
|
||||||
State::Canceled => State::Canceled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DragAndDrop<V> {
|
|
||||||
containers: HashSet<WeakViewHandle<V>>,
|
|
||||||
currently_dragged: Option<State<V>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V> Default for DragAndDrop<V> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
containers: Default::default(),
|
|
||||||
currently_dragged: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> DragAndDrop<V> {
|
|
||||||
pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
|
|
||||||
self.containers.insert(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn currently_dragged<T: Any>(&self, window: AnyWindowHandle) -> Option<(Vector2F, Rc<T>)> {
|
|
||||||
self.currently_dragged.as_ref().and_then(|state| {
|
|
||||||
if let State::Dragging {
|
|
||||||
position,
|
|
||||||
payload,
|
|
||||||
window: window_dragged_from,
|
|
||||||
..
|
|
||||||
} = state
|
|
||||||
{
|
|
||||||
if &window != window_dragged_from {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload
|
|
||||||
.is::<T>()
|
|
||||||
.then(|| payload.clone().downcast::<T>().ok())
|
|
||||||
.flatten()
|
|
||||||
.map(|payload| (position.clone(), payload))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any_currently_dragged(&self, window: AnyWindowHandle) -> bool {
|
|
||||||
self.currently_dragged
|
|
||||||
.as_ref()
|
|
||||||
.map(|state| {
|
|
||||||
if let State::Dragging {
|
|
||||||
window: window_dragged_from,
|
|
||||||
..
|
|
||||||
} = state
|
|
||||||
{
|
|
||||||
if &window != window_dragged_from {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_started(event: MouseDown, cx: &mut WindowContext) {
|
|
||||||
cx.update_global(|this: &mut Self, _| {
|
|
||||||
this.currently_dragged = Some(State::Down {
|
|
||||||
region_offset: event.position - event.region.origin(),
|
|
||||||
region: event.region,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dragging<T: Any>(
|
|
||||||
event: MouseDrag,
|
|
||||||
payload: Rc<T>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
render: Rc<impl 'static + Fn(&Modifiers, &T, &mut ViewContext<V>) -> AnyElement<V>>,
|
|
||||||
) {
|
|
||||||
let window = cx.window();
|
|
||||||
cx.update_global(|this: &mut Self, cx| {
|
|
||||||
this.notify_containers_for_window(window, cx);
|
|
||||||
|
|
||||||
match this.currently_dragged.as_ref() {
|
|
||||||
Some(&State::Down {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
})
|
|
||||||
| Some(&State::DeadZone {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
}) => {
|
|
||||||
if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
|
|
||||||
this.currently_dragged = Some(State::Dragging {
|
|
||||||
modifiers: event.modifiers,
|
|
||||||
window,
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
position: event.position,
|
|
||||||
payload,
|
|
||||||
render: Rc::new(move |modifiers, payload, cx| {
|
|
||||||
render(modifiers, payload.downcast_ref::<T>().unwrap(), cx)
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.currently_dragged = Some(State::DeadZone {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(&State::Dragging {
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
modifiers,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
this.currently_dragged = Some(State::Dragging {
|
|
||||||
modifiers,
|
|
||||||
window,
|
|
||||||
region_offset,
|
|
||||||
region,
|
|
||||||
position: event.position,
|
|
||||||
payload,
|
|
||||||
render: Rc::new(move |modifiers, payload, cx| {
|
|
||||||
render(modifiers, payload.downcast_ref::<T>().unwrap(), cx)
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_modifiers(new_modifiers: Modifiers, cx: &mut ViewContext<V>) -> bool {
|
|
||||||
let result = cx.update_global(|this: &mut Self, _| match &mut this.currently_dragged {
|
|
||||||
Some(state) => match state {
|
|
||||||
State::Dragging { modifiers, .. } => {
|
|
||||||
*modifiers = new_modifiers;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
None => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if result {
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(cx: &mut ViewContext<V>) -> Option<AnyElement<V>> {
|
|
||||||
enum DraggedElementHandler {}
|
|
||||||
cx.global::<Self>()
|
|
||||||
.currently_dragged
|
|
||||||
.clone()
|
|
||||||
.and_then(|state| {
|
|
||||||
match state {
|
|
||||||
State::Down { .. } => None,
|
|
||||||
State::DeadZone { .. } => None,
|
|
||||||
State::Dragging {
|
|
||||||
modifiers,
|
|
||||||
window,
|
|
||||||
region_offset,
|
|
||||||
position,
|
|
||||||
region,
|
|
||||||
payload,
|
|
||||||
render,
|
|
||||||
} => {
|
|
||||||
if cx.window() != window {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let position = (position - region_offset).round();
|
|
||||||
Some(
|
|
||||||
Overlay::new(
|
|
||||||
MouseEventHandler::new::<DraggedElementHandler, _>(
|
|
||||||
0,
|
|
||||||
cx,
|
|
||||||
|_, cx| render(&modifiers, payload, cx),
|
|
||||||
)
|
|
||||||
.with_cursor_style(CursorStyle::Arrow)
|
|
||||||
.on_up(MouseButton::Left, |_, _, cx| {
|
|
||||||
cx.window_context().defer(|cx| {
|
|
||||||
cx.update_global::<Self, _, _>(|this, cx| {
|
|
||||||
this.finish_dragging(cx)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
cx.propagate_event();
|
|
||||||
})
|
|
||||||
.on_up_out(MouseButton::Left, |_, _, cx| {
|
|
||||||
cx.window_context().defer(|cx| {
|
|
||||||
cx.update_global::<Self, _, _>(|this, cx| {
|
|
||||||
this.finish_dragging(cx)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
// Don't block hover events or invalidations
|
|
||||||
.with_hoverable(false)
|
|
||||||
.constrained()
|
|
||||||
.with_width(region.width())
|
|
||||||
.with_height(region.height()),
|
|
||||||
)
|
|
||||||
.with_anchor_position(position)
|
|
||||||
.into_any(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
State::Canceled => Some(
|
|
||||||
MouseEventHandler::new::<DraggedElementHandler, _>(0, cx, |_, _| {
|
|
||||||
Empty::new().constrained().with_width(0.).with_height(0.)
|
|
||||||
})
|
|
||||||
.on_up(MouseButton::Left, |_, _, cx| {
|
|
||||||
cx.window_context().defer(|cx| {
|
|
||||||
cx.update_global::<Self, _, _>(|this, _| {
|
|
||||||
this.currently_dragged = None;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on_up_out(MouseButton::Left, |_, _, cx| {
|
|
||||||
cx.window_context().defer(|cx| {
|
|
||||||
cx.update_global::<Self, _, _>(|this, _| {
|
|
||||||
this.currently_dragged = None;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.into_any(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
|
|
||||||
if let Some(State::Dragging {
|
|
||||||
payload, window, ..
|
|
||||||
}) = &self.currently_dragged
|
|
||||||
{
|
|
||||||
if payload.is::<P>() {
|
|
||||||
let window = *window;
|
|
||||||
self.currently_dragged = Some(State::Canceled);
|
|
||||||
self.notify_containers_for_window(window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_dragging(&mut self, cx: &mut WindowContext) {
|
|
||||||
if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
|
|
||||||
self.notify_containers_for_window(window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
|
|
||||||
self.containers.retain(|container| {
|
|
||||||
if let Some(container) = container.upgrade(cx) {
|
|
||||||
if container.window() == window {
|
|
||||||
container.update(cx, |_, cx| cx.notify());
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Draggable<V> {
|
|
||||||
fn as_draggable<D: View, P: Any>(
|
|
||||||
self,
|
|
||||||
payload: P,
|
|
||||||
render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext<D>) -> AnyElement<D>,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> Draggable<V> for MouseEventHandler<V> {
|
|
||||||
fn as_draggable<D: View, P: Any>(
|
|
||||||
self,
|
|
||||||
payload: P,
|
|
||||||
render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext<D>) -> AnyElement<D>,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let payload = Rc::new(payload);
|
|
||||||
let render = Rc::new(render);
|
|
||||||
self.on_down(MouseButton::Left, move |e, _, cx| {
|
|
||||||
cx.propagate_event();
|
|
||||||
DragAndDrop::<D>::drag_started(e, cx);
|
|
||||||
})
|
|
||||||
.on_drag(MouseButton::Left, move |e, _, cx| {
|
|
||||||
if e.end {
|
|
||||||
cx.update_global::<DragAndDrop<D>, _, _>(|drag_and_drop, cx| {
|
|
||||||
drag_and_drop.finish_dragging(cx)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let payload = payload.clone();
|
|
||||||
let render = render.clone();
|
|
||||||
DragAndDrop::<D>::dragging(e, payload, cx, render)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,7 @@ doctest = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
gpui2_macros = { path = "../gpui2_macros" }
|
gpui_macros = { path = "../gpui_macros" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
sqlez = { path = "../sqlez" }
|
sqlez = { path = "../sqlez" }
|
||||||
|
@ -47,7 +47,7 @@ pub use element::*;
|
|||||||
pub use elements::*;
|
pub use elements::*;
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
pub use geometry::*;
|
pub use geometry::*;
|
||||||
pub use gpui2_macros::*;
|
pub use gpui_macros::*;
|
||||||
pub use image_cache::*;
|
pub use image_cache::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use interactive::*;
|
pub use interactive::*;
|
||||||
|
@ -10,7 +10,7 @@ use taffy::style::Overflow;
|
|||||||
pub trait Styled: Sized {
|
pub trait Styled: Sized {
|
||||||
fn style(&mut self) -> &mut StyleRefinement;
|
fn style(&mut self) -> &mut StyleRefinement;
|
||||||
|
|
||||||
gpui2_macros::style_helpers!();
|
gpui_macros::style_helpers!();
|
||||||
|
|
||||||
fn z_index(mut self, z_index: u8) -> Self {
|
fn z_index(mut self, z_index: u8) -> Self {
|
||||||
self.style().z_index = Some(z_index);
|
self.style().z_index = Some(z_index);
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use gpui2::{actions, impl_actions};
|
use gpui::{actions, impl_actions};
|
||||||
use gpui2_macros::register_action;
|
use gpui_macros::register_action;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_action_macros() {
|
fn test_action_macros() {
|
||||||
use gpui2 as gpui;
|
|
||||||
|
|
||||||
actions!(test, [TestAction]);
|
actions!(test, [TestAction]);
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize)]
|
#[derive(PartialEq, Clone, Deserialize)]
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "gpui2_macros"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/gpui2_macros.rs"
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syn = { version = "1.0.72", features = ["full"] }
|
|
||||||
quote = "1.0.9"
|
|
||||||
proc-macro2 = "1.0.66"
|
|
@ -1,32 +0,0 @@
|
|||||||
mod derive_into_element;
|
|
||||||
mod derive_render;
|
|
||||||
mod register_action;
|
|
||||||
mod style_helpers;
|
|
||||||
mod test;
|
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
|
||||||
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn register_action(ident: TokenStream) -> TokenStream {
|
|
||||||
register_action::register_action_macro(ident)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(IntoElement)]
|
|
||||||
pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
|
||||||
derive_into_element::derive_into_element(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(Render)]
|
|
||||||
pub fn derive_render(input: TokenStream) -> TokenStream {
|
|
||||||
derive_render::derive_render(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn style_helpers(input: TokenStream) -> TokenStream {
|
|
||||||
style_helpers::style_helpers(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
|
||||||
test::test(args, function)
|
|
||||||
}
|
|
@ -7,10 +7,8 @@ publish = false
|
|||||||
[lib]
|
[lib]
|
||||||
path = "src/gpui_macros.rs"
|
path = "src/gpui_macros.rs"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static.workspace = true
|
syn = { version = "1.0.72", features = ["full"] }
|
||||||
proc-macro2 = "1.0"
|
quote = "1.0.9"
|
||||||
syn = "1.0"
|
proc-macro2 = "1.0.66"
|
||||||
quote = "1.0"
|
|
||||||
|
@ -13,7 +13,7 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
|||||||
{
|
{
|
||||||
type Element = gpui::Component<Self>;
|
type Element = gpui::Component<Self>;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
fn element_id(&self) -> Option<gpui::ElementId> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
|
|||||||
impl #impl_generics gpui::Render for #type_name #type_generics
|
impl #impl_generics gpui::Render for #type_name #type_generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl Element {
|
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::Element {
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,381 +1,32 @@
|
|||||||
|
mod derive_into_element;
|
||||||
|
mod derive_render;
|
||||||
|
mod register_action;
|
||||||
|
mod style_helpers;
|
||||||
|
mod test;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Ident;
|
|
||||||
use quote::{format_ident, quote};
|
#[proc_macro]
|
||||||
use std::mem;
|
pub fn register_action(ident: TokenStream) -> TokenStream {
|
||||||
use syn::{
|
register_action::register_action_macro(ident)
|
||||||
parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
|
}
|
||||||
GenericParam, Generics, ItemFn, Lit, Meta, NestedMeta, Type, WhereClause,
|
|
||||||
};
|
#[proc_macro_derive(IntoElement)]
|
||||||
|
pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
||||||
|
derive_into_element::derive_into_element(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(Render)]
|
||||||
|
pub fn derive_render(input: TokenStream) -> TokenStream {
|
||||||
|
derive_render::derive_render(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn style_helpers(input: TokenStream) -> TokenStream {
|
||||||
|
style_helpers::style_helpers(input)
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||||
let mut namespace = format_ident!("gpui");
|
test::test(args, function)
|
||||||
|
|
||||||
let args = syn::parse_macro_input!(args as AttributeArgs);
|
|
||||||
let mut max_retries = 0;
|
|
||||||
let mut num_iterations = 1;
|
|
||||||
let mut starting_seed = 0;
|
|
||||||
let mut detect_nondeterminism = false;
|
|
||||||
let mut on_failure_fn_name = quote!(None);
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
match arg {
|
|
||||||
NestedMeta::Meta(Meta::Path(name))
|
|
||||||
if name.get_ident().map_or(false, |n| n == "self") =>
|
|
||||||
{
|
|
||||||
namespace = format_ident!("crate");
|
|
||||||
}
|
|
||||||
NestedMeta::Meta(Meta::NameValue(meta)) => {
|
|
||||||
let key_name = meta.path.get_ident().map(|i| i.to_string());
|
|
||||||
let result = (|| {
|
|
||||||
match key_name.as_deref() {
|
|
||||||
Some("detect_nondeterminism") => {
|
|
||||||
detect_nondeterminism = parse_bool(&meta.lit)?
|
|
||||||
}
|
|
||||||
Some("retries") => max_retries = parse_int(&meta.lit)?,
|
|
||||||
Some("iterations") => num_iterations = parse_int(&meta.lit)?,
|
|
||||||
Some("seed") => starting_seed = parse_int(&meta.lit)?,
|
|
||||||
Some("on_failure") => {
|
|
||||||
if let Lit::Str(name) = meta.lit {
|
|
||||||
let mut path = syn::Path {
|
|
||||||
leading_colon: None,
|
|
||||||
segments: Default::default(),
|
|
||||||
};
|
|
||||||
for part in name.value().split("::") {
|
|
||||||
path.segments.push(Ident::new(part, name.span()).into());
|
|
||||||
}
|
|
||||||
on_failure_fn_name = quote!(Some(#path));
|
|
||||||
} else {
|
|
||||||
return Err(TokenStream::from(
|
|
||||||
syn::Error::new(
|
|
||||||
meta.lit.span(),
|
|
||||||
"on_failure argument must be a string",
|
|
||||||
)
|
|
||||||
.into_compile_error(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(TokenStream::from(
|
|
||||||
syn::Error::new(meta.path.span(), "invalid argument")
|
|
||||||
.into_compile_error(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})();
|
|
||||||
|
|
||||||
if let Err(tokens) = result {
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return TokenStream::from(
|
|
||||||
syn::Error::new_spanned(other, "invalid argument").into_compile_error(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut inner_fn = parse_macro_input!(function as ItemFn);
|
|
||||||
if max_retries > 0 && num_iterations > 1 {
|
|
||||||
return TokenStream::from(
|
|
||||||
syn::Error::new_spanned(inner_fn, "retries and randomized iterations can't be mixed")
|
|
||||||
.into_compile_error(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let inner_fn_attributes = mem::take(&mut inner_fn.attrs);
|
|
||||||
let inner_fn_name = format_ident!("_{}", inner_fn.sig.ident);
|
|
||||||
let outer_fn_name = mem::replace(&mut inner_fn.sig.ident, inner_fn_name.clone());
|
|
||||||
|
|
||||||
let mut outer_fn: ItemFn = if inner_fn.sig.asyncness.is_some() {
|
|
||||||
// Pass to the test function the number of app contexts that it needs,
|
|
||||||
// based on its parameter list.
|
|
||||||
let mut cx_vars = proc_macro2::TokenStream::new();
|
|
||||||
let mut cx_teardowns = proc_macro2::TokenStream::new();
|
|
||||||
let mut inner_fn_args = proc_macro2::TokenStream::new();
|
|
||||||
for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
|
|
||||||
if let FnArg::Typed(arg) = arg {
|
|
||||||
if let Type::Path(ty) = &*arg.ty {
|
|
||||||
let last_segment = ty.path.segments.last();
|
|
||||||
match last_segment.map(|s| s.ident.to_string()).as_deref() {
|
|
||||||
Some("StdRng") => {
|
|
||||||
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some("Arc") => {
|
|
||||||
if let syn::PathArguments::AngleBracketed(args) =
|
|
||||||
&last_segment.unwrap().arguments
|
|
||||||
{
|
|
||||||
if let Some(syn::GenericArgument::Type(syn::Type::Path(ty))) =
|
|
||||||
args.args.last()
|
|
||||||
{
|
|
||||||
let last_segment = ty.path.segments.last();
|
|
||||||
if let Some("Deterministic") =
|
|
||||||
last_segment.map(|s| s.ident.to_string()).as_deref()
|
|
||||||
{
|
|
||||||
inner_fn_args.extend(quote!(deterministic.clone(),));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else if let Type::Reference(ty) = &*arg.ty {
|
|
||||||
if let Type::Path(ty) = &*ty.elem {
|
|
||||||
let last_segment = ty.path.segments.last();
|
|
||||||
if let Some("TestAppContext") =
|
|
||||||
last_segment.map(|s| s.ident.to_string()).as_deref()
|
|
||||||
{
|
|
||||||
let first_entity_id = ix * 100_000;
|
|
||||||
let cx_varname = format_ident!("cx_{}", ix);
|
|
||||||
cx_vars.extend(quote!(
|
|
||||||
let mut #cx_varname = #namespace::TestAppContext::new(
|
|
||||||
foreground_platform.clone(),
|
|
||||||
cx.platform().clone(),
|
|
||||||
deterministic.build_foreground(#ix),
|
|
||||||
deterministic.build_background(),
|
|
||||||
cx.font_cache().clone(),
|
|
||||||
cx.leak_detector(),
|
|
||||||
#first_entity_id,
|
|
||||||
stringify!(#outer_fn_name).to_string(),
|
|
||||||
);
|
|
||||||
));
|
|
||||||
cx_teardowns.extend(quote!(
|
|
||||||
#cx_varname.remove_all_windows();
|
|
||||||
deterministic.run_until_parked();
|
|
||||||
#cx_varname.update(|cx| cx.clear_globals());
|
|
||||||
));
|
|
||||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TokenStream::from(
|
|
||||||
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_quote! {
|
|
||||||
#[test]
|
|
||||||
fn #outer_fn_name() {
|
|
||||||
#inner_fn
|
|
||||||
|
|
||||||
#namespace::test::run_test(
|
|
||||||
#num_iterations as u64,
|
|
||||||
#starting_seed as u64,
|
|
||||||
#max_retries,
|
|
||||||
#detect_nondeterminism,
|
|
||||||
&mut |cx, foreground_platform, deterministic, seed| {
|
|
||||||
// some of the macro contents do not use all variables, silence the warnings
|
|
||||||
let _ = (&cx, &foreground_platform, &deterministic, &seed);
|
|
||||||
#cx_vars
|
|
||||||
cx.foreground().run(#inner_fn_name(#inner_fn_args));
|
|
||||||
#cx_teardowns
|
|
||||||
},
|
|
||||||
#on_failure_fn_name,
|
|
||||||
stringify!(#outer_fn_name).to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Pass to the test function the number of app contexts that it needs,
|
|
||||||
// based on its parameter list.
|
|
||||||
let mut cx_vars = proc_macro2::TokenStream::new();
|
|
||||||
let mut cx_teardowns = proc_macro2::TokenStream::new();
|
|
||||||
let mut inner_fn_args = proc_macro2::TokenStream::new();
|
|
||||||
for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
|
|
||||||
if let FnArg::Typed(arg) = arg {
|
|
||||||
if let Type::Path(ty) = &*arg.ty {
|
|
||||||
let last_segment = ty.path.segments.last();
|
|
||||||
|
|
||||||
if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
|
|
||||||
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if let Type::Reference(ty) = &*arg.ty {
|
|
||||||
if let Type::Path(ty) = &*ty.elem {
|
|
||||||
let last_segment = ty.path.segments.last();
|
|
||||||
match last_segment.map(|s| s.ident.to_string()).as_deref() {
|
|
||||||
Some("AppContext") => {
|
|
||||||
inner_fn_args.extend(quote!(cx,));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some("TestAppContext") => {
|
|
||||||
let first_entity_id = ix * 100_000;
|
|
||||||
let cx_varname = format_ident!("cx_{}", ix);
|
|
||||||
cx_vars.extend(quote!(
|
|
||||||
let mut #cx_varname = #namespace::TestAppContext::new(
|
|
||||||
foreground_platform.clone(),
|
|
||||||
cx.platform().clone(),
|
|
||||||
deterministic.build_foreground(#ix),
|
|
||||||
deterministic.build_background(),
|
|
||||||
cx.font_cache().clone(),
|
|
||||||
cx.leak_detector(),
|
|
||||||
#first_entity_id,
|
|
||||||
stringify!(#outer_fn_name).to_string(),
|
|
||||||
);
|
|
||||||
));
|
|
||||||
cx_teardowns.extend(quote!(
|
|
||||||
#cx_varname.remove_all_windows();
|
|
||||||
deterministic.run_until_parked();
|
|
||||||
#cx_varname.update(|cx| cx.clear_globals());
|
|
||||||
));
|
|
||||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TokenStream::from(
|
|
||||||
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_quote! {
|
|
||||||
#[test]
|
|
||||||
fn #outer_fn_name() {
|
|
||||||
#inner_fn
|
|
||||||
|
|
||||||
#namespace::test::run_test(
|
|
||||||
#num_iterations as u64,
|
|
||||||
#starting_seed as u64,
|
|
||||||
#max_retries,
|
|
||||||
#detect_nondeterminism,
|
|
||||||
&mut |cx, foreground_platform, deterministic, seed| {
|
|
||||||
// some of the macro contents do not use all variables, silence the warnings
|
|
||||||
let _ = (&cx, &foreground_platform, &deterministic, &seed);
|
|
||||||
#cx_vars
|
|
||||||
#inner_fn_name(#inner_fn_args);
|
|
||||||
#cx_teardowns
|
|
||||||
},
|
|
||||||
#on_failure_fn_name,
|
|
||||||
stringify!(#outer_fn_name).to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
outer_fn.attrs.extend(inner_fn_attributes);
|
|
||||||
|
|
||||||
TokenStream::from(quote!(#outer_fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_int(literal: &Lit) -> Result<usize, TokenStream> {
|
|
||||||
let result = if let Lit::Int(int) = &literal {
|
|
||||||
int.base10_parse()
|
|
||||||
} else {
|
|
||||||
Err(syn::Error::new(literal.span(), "must be an integer"))
|
|
||||||
};
|
|
||||||
|
|
||||||
result.map_err(|err| TokenStream::from(err.into_compile_error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
|
|
||||||
let result = if let Lit::Bool(result) = &literal {
|
|
||||||
Ok(result.value)
|
|
||||||
} else {
|
|
||||||
Err(syn::Error::new(literal.span(), "must be a boolean"))
|
|
||||||
};
|
|
||||||
|
|
||||||
result.map_err(|err| TokenStream::from(err.into_compile_error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(Element)]
|
|
||||||
pub fn element_derive(input: TokenStream) -> TokenStream {
|
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
|
||||||
let type_name = ast.ident;
|
|
||||||
|
|
||||||
let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
|
|
||||||
let placeholder_view_type_name: Ident = parse_quote! { V };
|
|
||||||
let view_type_name: Ident;
|
|
||||||
let impl_generics: syn::ImplGenerics<'_>;
|
|
||||||
let type_generics: Option<syn::TypeGenerics<'_>>;
|
|
||||||
let where_clause: Option<&'_ WhereClause>;
|
|
||||||
|
|
||||||
match ast.generics.params.iter().find_map(|param| {
|
|
||||||
if let GenericParam::Type(type_param) = param {
|
|
||||||
Some(type_param.ident.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some(type_name) => {
|
|
||||||
view_type_name = type_name;
|
|
||||||
let generics = ast.generics.split_for_impl();
|
|
||||||
impl_generics = generics.0;
|
|
||||||
type_generics = Some(generics.1);
|
|
||||||
where_clause = generics.2;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
view_type_name = placeholder_view_type_name;
|
|
||||||
let generics = placeholder_view_generics.split_for_impl();
|
|
||||||
impl_generics = generics.0;
|
|
||||||
type_generics = None;
|
|
||||||
where_clause = generics.2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
impl #impl_generics Element<#view_type_name> for #type_name #type_generics
|
|
||||||
#where_clause
|
|
||||||
{
|
|
||||||
|
|
||||||
type LayoutState = gpui::elements::AnyElement<V>;
|
|
||||||
type PaintState = ();
|
|
||||||
|
|
||||||
fn layout(
|
|
||||||
&mut self,
|
|
||||||
constraint: gpui::SizeConstraint,
|
|
||||||
view: &mut V,
|
|
||||||
cx: &mut gpui::ViewContext<V>,
|
|
||||||
) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
|
|
||||||
let mut element = self.render(view, cx).into_any();
|
|
||||||
let size = element.layout(constraint, view, cx);
|
|
||||||
(size, element)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
bounds: gpui::geometry::rect::RectF,
|
|
||||||
visible_bounds: gpui::geometry::rect::RectF,
|
|
||||||
element: &mut gpui::elements::AnyElement<V>,
|
|
||||||
view: &mut V,
|
|
||||||
cx: &mut gpui::ViewContext<V>,
|
|
||||||
) {
|
|
||||||
element.paint(bounds.origin(), visible_bounds, view, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect_for_text_range(
|
|
||||||
&self,
|
|
||||||
range_utf16: std::ops::Range<usize>,
|
|
||||||
_: gpui::geometry::rect::RectF,
|
|
||||||
_: gpui::geometry::rect::RectF,
|
|
||||||
element: &gpui::elements::AnyElement<V>,
|
|
||||||
_: &(),
|
|
||||||
view: &V,
|
|
||||||
cx: &gpui::ViewContext<V>,
|
|
||||||
) -> Option<gpui::geometry::rect::RectF> {
|
|
||||||
element.rect_for_text_range(range_utf16, view, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug(
|
|
||||||
&self,
|
|
||||||
_: gpui::geometry::rect::RectF,
|
|
||||||
element: &gpui::elements::AnyElement<V>,
|
|
||||||
_: &(),
|
|
||||||
view: &V,
|
|
||||||
cx: &gpui::ViewContext<V>,
|
|
||||||
) -> gpui::json::Value {
|
|
||||||
element.debug(view, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
gen.into()
|
|
||||||
}
|
}
|
||||||
|
@ -342,7 +342,7 @@ impl Peer {
|
|||||||
pub fn add_test_connection(
|
pub fn add_test_connection(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
executor: Arc<gpui::executor::Background>,
|
executor: gpui::BackgroundExecutor,
|
||||||
) -> (
|
) -> (
|
||||||
ConnectionId,
|
ConnectionId,
|
||||||
impl Future<Output = anyhow::Result<()>> + Send,
|
impl Future<Output = anyhow::Result<()>> + Send,
|
||||||
@ -559,7 +559,6 @@ mod tests {
|
|||||||
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
|
||||||
#[ctor::ctor]
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
if std::env::var("RUST_LOG").is_ok() {
|
if std::env::var("RUST_LOG").is_ok() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
@ -568,7 +567,9 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_request_response(cx: &mut TestAppContext) {
|
async fn test_request_response(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
init_logger();
|
||||||
|
|
||||||
|
let executor = cx.executor();
|
||||||
|
|
||||||
// create 2 clients connected to 1 server
|
// create 2 clients connected to 1 server
|
||||||
let server = Peer::new(0);
|
let server = Peer::new(0);
|
||||||
@ -576,18 +577,18 @@ mod tests {
|
|||||||
let client2 = Peer::new(0);
|
let client2 = Peer::new(0);
|
||||||
|
|
||||||
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.executor());
|
||||||
let (client1_conn_id, io_task1, client1_incoming) =
|
let (client1_conn_id, io_task1, client1_incoming) =
|
||||||
client1.add_test_connection(client1_to_server_conn, cx.background());
|
client1.add_test_connection(client1_to_server_conn, cx.executor());
|
||||||
let (_, io_task2, server_incoming1) =
|
let (_, io_task2, server_incoming1) =
|
||||||
server.add_test_connection(server_to_client_1_conn, cx.background());
|
server.add_test_connection(server_to_client_1_conn, cx.executor());
|
||||||
|
|
||||||
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
|
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.executor());
|
||||||
let (client2_conn_id, io_task3, client2_incoming) =
|
let (client2_conn_id, io_task3, client2_incoming) =
|
||||||
client2.add_test_connection(client2_to_server_conn, cx.background());
|
client2.add_test_connection(client2_to_server_conn, cx.executor());
|
||||||
let (_, io_task4, server_incoming2) =
|
let (_, io_task4, server_incoming2) =
|
||||||
server.add_test_connection(server_to_client_2_conn, cx.background());
|
server.add_test_connection(server_to_client_2_conn, cx.executor());
|
||||||
|
|
||||||
executor.spawn(io_task1).detach();
|
executor.spawn(io_task1).detach();
|
||||||
executor.spawn(io_task2).detach();
|
executor.spawn(io_task2).detach();
|
||||||
@ -664,25 +665,25 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
|
async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
let executor = cx.executor();
|
||||||
let server = Peer::new(0);
|
let server = Peer::new(0);
|
||||||
let client = Peer::new(0);
|
let client = Peer::new(0);
|
||||||
|
|
||||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(executor.clone());
|
||||||
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||||
client.add_test_connection(client_to_server_conn, cx.background());
|
client.add_test_connection(client_to_server_conn, executor.clone());
|
||||||
|
|
||||||
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||||
server.add_test_connection(server_to_client_conn, cx.background());
|
server.add_test_connection(server_to_client_conn, executor.clone());
|
||||||
|
|
||||||
executor.spawn(io_task1).detach();
|
executor.spawn(io_task1).detach();
|
||||||
executor.spawn(io_task2).detach();
|
executor.spawn(io_task2).detach();
|
||||||
|
|
||||||
executor
|
executor
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let request = server_incoming
|
let future = server_incoming.next().await;
|
||||||
.next()
|
let request = future
|
||||||
.await
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_any()
|
.into_any()
|
||||||
.downcast::<TypedEnvelope<proto::Ping>>()
|
.downcast::<TypedEnvelope<proto::Ping>>()
|
||||||
@ -762,16 +763,16 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
|
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
let executor = cx.executor();
|
||||||
let server = Peer::new(0);
|
let server = Peer::new(0);
|
||||||
let client = Peer::new(0);
|
let client = Peer::new(0);
|
||||||
|
|
||||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.executor());
|
||||||
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||||
client.add_test_connection(client_to_server_conn, cx.background());
|
client.add_test_connection(client_to_server_conn, cx.executor());
|
||||||
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||||
server.add_test_connection(server_to_client_conn, cx.background());
|
server.add_test_connection(server_to_client_conn, cx.executor());
|
||||||
|
|
||||||
executor.spawn(io_task1).detach();
|
executor.spawn(io_task1).detach();
|
||||||
executor.spawn(io_task2).detach();
|
executor.spawn(io_task2).detach();
|
||||||
@ -858,7 +859,7 @@ mod tests {
|
|||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
// Allow the request to make some progress before dropping it.
|
// Allow the request to make some progress before dropping it.
|
||||||
cx.background().simulate_random_delay().await;
|
cx.executor().simulate_random_delay().await;
|
||||||
drop(request1_task);
|
drop(request1_task);
|
||||||
|
|
||||||
request2_task.await;
|
request2_task.await;
|
||||||
@ -874,13 +875,13 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_disconnect(cx: &mut TestAppContext) {
|
async fn test_disconnect(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
let executor = cx.executor();
|
||||||
|
|
||||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
|
||||||
|
|
||||||
let client = Peer::new(0);
|
let client = Peer::new(0);
|
||||||
let (connection_id, io_handler, mut incoming) =
|
let (connection_id, io_handler, mut incoming) =
|
||||||
client.add_test_connection(client_conn, cx.background());
|
client.add_test_connection(client_conn, executor.clone());
|
||||||
|
|
||||||
let (io_ended_tx, io_ended_rx) = oneshot::channel();
|
let (io_ended_tx, io_ended_rx) = oneshot::channel();
|
||||||
executor
|
executor
|
||||||
@ -910,12 +911,12 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_io_error(cx: &mut TestAppContext) {
|
async fn test_io_error(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
let executor = cx.executor();
|
||||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
|
||||||
|
|
||||||
let client = Peer::new(0);
|
let client = Peer::new(0);
|
||||||
let (connection_id, io_handler, mut incoming) =
|
let (connection_id, io_handler, mut incoming) =
|
||||||
client.add_test_connection(client_conn, cx.background());
|
client.add_test_connection(client_conn, executor.clone());
|
||||||
executor.spawn(io_handler).detach();
|
executor.spawn(io_handler).detach();
|
||||||
executor
|
executor
|
||||||
.spawn(async move { incoming.next().await })
|
.spawn(async move { incoming.next().await })
|
||||||
|
@ -31,6 +31,7 @@ smallvec.workspace = true
|
|||||||
story = { path = "../story" }
|
story = { path = "../story" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
|
ui = { path = "../ui", features = ["stories"] }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
picker = { path = "../picker" }
|
picker = { path = "../picker" }
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyElement, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
|
div, AnyElement, Element, IntoElement, ParentElement, RenderOnce, Styled,
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::v_stack;
|
use crate::v_stack;
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "xtask"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0"
|
|
||||||
clap = {version = "4.0", features = ["derive"]}
|
|
||||||
theme = {path = "../theme"}
|
|
||||||
serde_json.workspace = true
|
|
||||||
schemars.workspace = true
|
|
@ -1,23 +0,0 @@
|
|||||||
use clap::{Parser, Subcommand};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
/// Common utilities for Zed developers.
|
|
||||||
// For more information, see [matklad's repository README](https://github.com/matklad/cargo-xtask/)
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
#[command(propagate_version = true)]
|
|
||||||
pub struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Command to run.
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum Commands {
|
|
||||||
/// Builds theme types for interop with Typescript.
|
|
||||||
BuildThemeTypes {
|
|
||||||
#[clap(short, long, default_value = "schemas")]
|
|
||||||
out_dir: PathBuf,
|
|
||||||
#[clap(short, long, default_value = "theme.json")]
|
|
||||||
file_name: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
mod cli;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Parser;
|
|
||||||
use schemars::schema_for;
|
|
||||||
use theme::Theme;
|
|
||||||
|
|
||||||
fn build_themes(out_dir: PathBuf, file_name: PathBuf) -> Result<()> {
|
|
||||||
let theme = schema_for!(Theme);
|
|
||||||
let output = serde_json::to_string_pretty(&theme)?;
|
|
||||||
|
|
||||||
std::fs::create_dir(&out_dir)?;
|
|
||||||
|
|
||||||
let mut file_path = out_dir;
|
|
||||||
file_path.push(file_name);
|
|
||||||
|
|
||||||
std::fs::write(file_path, output)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let args = cli::Cli::parse();
|
|
||||||
match args.command {
|
|
||||||
cli::Commands::BuildThemeTypes { out_dir, file_name } => build_themes(out_dir, file_name),
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user