Merge 5cf16c80c4
into 0e575b2809
This commit is contained in:
commit
b7a3a3f721
1 changed files with 252 additions and 57 deletions
|
@ -13,6 +13,7 @@ use futures::FutureExt;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use futures::select;
|
use futures::select;
|
||||||
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::ClickEvent;
|
use gpui::ClickEvent;
|
||||||
use gpui::ClipboardItem;
|
use gpui::ClipboardItem;
|
||||||
use gpui::Subscription;
|
use gpui::Subscription;
|
||||||
|
@ -67,13 +68,15 @@ use crate::ssh_connections::open_ssh_project;
|
||||||
mod navigation_base {}
|
mod navigation_base {}
|
||||||
pub struct RemoteServerProjects {
|
pub struct RemoteServerProjects {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
focus_handle: FocusHandle,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
retained_connections: Vec<Entity<SshRemoteClient>>,
|
retained_connections: Vec<Entity<SshRemoteClient>>,
|
||||||
ssh_config_updates: Task<()>,
|
ssh_config_updates: Task<()>,
|
||||||
ssh_config_servers: BTreeSet<SharedString>,
|
ssh_config_servers: BTreeSet<SharedString>,
|
||||||
create_new_window: bool,
|
create_new_window: bool,
|
||||||
|
search_editor: Entity<Editor>,
|
||||||
|
filtered_servers: Vec<(RemoteEntry, Vec<StringMatch>)>,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
|
_search_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CreateRemoteServer {
|
struct CreateRemoteServer {
|
||||||
|
@ -380,7 +383,6 @@ impl RemoteServerProjects {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let focus_handle = cx.focus_handle();
|
|
||||||
let mut read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
|
let mut read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
|
||||||
let ssh_config_updates = if read_ssh_config {
|
let ssh_config_updates = if read_ssh_config {
|
||||||
spawn_ssh_config_watch(fs.clone(), cx)
|
spawn_ssh_config_watch(fs.clone(), cx)
|
||||||
|
@ -394,6 +396,26 @@ impl RemoteServerProjects {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let search_editor = cx.new(|cx| {
|
||||||
|
let mut editor = Editor::single_line(window, cx);
|
||||||
|
editor.set_placeholder_text("Search remote servers and projects...", cx);
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up search editor change listener to update filtering
|
||||||
|
let search_editor_subscription = cx.subscribe(
|
||||||
|
&search_editor,
|
||||||
|
|_this, _editor, _event: &editor::EditorEvent, cx| {
|
||||||
|
cx.notify();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Focus the search editor initially
|
||||||
|
let search_editor_for_focus = search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor_for_focus.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
|
|
||||||
let _subscription =
|
let _subscription =
|
||||||
cx.observe_global_in::<SettingsStore>(window, move |recent_projects, _, cx| {
|
cx.observe_global_in::<SettingsStore>(window, move |recent_projects, _, cx| {
|
||||||
let new_read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
|
let new_read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
|
||||||
|
@ -408,16 +430,143 @@ impl RemoteServerProjects {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
let mut this = Self {
|
||||||
mode: Mode::default_mode(&BTreeSet::new(), cx),
|
mode: Mode::default_mode(&BTreeSet::new(), cx),
|
||||||
focus_handle,
|
|
||||||
workspace,
|
workspace,
|
||||||
retained_connections: Vec::new(),
|
retained_connections: Vec::new(),
|
||||||
ssh_config_updates,
|
ssh_config_updates,
|
||||||
ssh_config_servers: BTreeSet::new(),
|
ssh_config_servers: BTreeSet::new(),
|
||||||
create_new_window,
|
create_new_window,
|
||||||
|
search_editor,
|
||||||
|
filtered_servers: Vec::new(),
|
||||||
_subscription,
|
_subscription,
|
||||||
|
_search_subscription: search_editor_subscription,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update_filtered_servers("", cx);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_filtered_servers(&mut self, query: &str, cx: &mut Context<Self>) {
|
||||||
|
let ssh_settings = SshSettings::get_global(cx);
|
||||||
|
let read_ssh_config = ssh_settings.read_ssh_config;
|
||||||
|
|
||||||
|
let mut servers: Vec<RemoteEntry> = ssh_settings
|
||||||
|
.ssh_connections()
|
||||||
|
.map(|connection| {
|
||||||
|
let open_folder = NavigableEntry::new(&ScrollHandle::new(), cx);
|
||||||
|
let configure = NavigableEntry::new(&ScrollHandle::new(), cx);
|
||||||
|
let projects = connection
|
||||||
|
.projects
|
||||||
|
.iter()
|
||||||
|
.map(|project| {
|
||||||
|
(
|
||||||
|
NavigableEntry::new(&ScrollHandle::new(), cx),
|
||||||
|
project.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
RemoteEntry::Project {
|
||||||
|
open_folder,
|
||||||
|
configure,
|
||||||
|
projects,
|
||||||
|
connection,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if read_ssh_config {
|
||||||
|
let mut extra_servers_from_config = self.ssh_config_servers.clone();
|
||||||
|
for server in &servers {
|
||||||
|
if let RemoteEntry::Project { connection, .. } = server {
|
||||||
|
extra_servers_from_config.remove(&connection.host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
servers.extend(extra_servers_from_config.into_iter().map(|host| {
|
||||||
|
RemoteEntry::SshConfig {
|
||||||
|
open_folder: NavigableEntry::new(&ScrollHandle::new(), cx),
|
||||||
|
host,
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.trim().is_empty() {
|
||||||
|
self.filtered_servers = servers
|
||||||
|
.into_iter()
|
||||||
|
.map(|server| (server, Vec::new()))
|
||||||
|
.collect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = query.trim();
|
||||||
|
let smart_case = query.chars().any(|c| c.is_uppercase());
|
||||||
|
|
||||||
|
// Instead of matching entire servers, we need to match individual projects within servers
|
||||||
|
let mut filtered_servers = Vec::new();
|
||||||
|
|
||||||
|
for server in servers {
|
||||||
|
match &server {
|
||||||
|
RemoteEntry::Project { connection, .. } => {
|
||||||
|
// Filter projects within this server
|
||||||
|
let mut matching_projects = Vec::new();
|
||||||
|
|
||||||
|
for project in &connection.projects {
|
||||||
|
let project_paths = project.paths.join(",");
|
||||||
|
let search_string = format!("{}:{}", connection.host, project_paths);
|
||||||
|
|
||||||
|
// Create candidate for this specific project
|
||||||
|
let candidate = StringMatchCandidate::new(0, &search_string);
|
||||||
|
let matches = smol::block_on(fuzzy::match_strings(
|
||||||
|
&[candidate],
|
||||||
|
query,
|
||||||
|
smart_case,
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
&Default::default(),
|
||||||
|
cx.background_executor().clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
if !matches.is_empty() {
|
||||||
|
matching_projects.push((
|
||||||
|
NavigableEntry::new(&ScrollHandle::new(), cx),
|
||||||
|
project.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any projects match, include the server with only matching projects
|
||||||
|
if !matching_projects.is_empty() {
|
||||||
|
let filtered_server = RemoteEntry::Project {
|
||||||
|
open_folder: NavigableEntry::new(&ScrollHandle::new(), cx),
|
||||||
|
configure: NavigableEntry::new(&ScrollHandle::new(), cx),
|
||||||
|
projects: matching_projects,
|
||||||
|
connection: connection.clone(),
|
||||||
|
};
|
||||||
|
filtered_servers.push((filtered_server, Vec::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoteEntry::SshConfig { host, .. } => {
|
||||||
|
let search_string = host.to_string();
|
||||||
|
|
||||||
|
let candidate = StringMatchCandidate::new(0, &search_string);
|
||||||
|
let matches = smol::block_on(fuzzy::match_strings(
|
||||||
|
&[candidate],
|
||||||
|
query,
|
||||||
|
smart_case,
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
&Default::default(),
|
||||||
|
cx.background_executor().clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
if !matches.is_empty() {
|
||||||
|
filtered_servers.push((server, Vec::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.filtered_servers = filtered_servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project_picker(
|
pub fn project_picker(
|
||||||
|
@ -492,7 +641,10 @@ impl RemoteServerProjects {
|
||||||
this.retained_connections.push(client);
|
this.retained_connections.push(client);
|
||||||
this.add_ssh_server(connection_options, cx);
|
this.add_ssh_server(connection_options, cx);
|
||||||
this.mode = Mode::default_mode(&this.ssh_config_servers, cx);
|
this.mode = Mode::default_mode(&this.ssh_config_servers, cx);
|
||||||
this.focus_handle(cx).focus(window);
|
let search_editor = this.search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
cx.notify()
|
cx.notify()
|
||||||
})
|
})
|
||||||
.log_err(),
|
.log_err(),
|
||||||
|
@ -536,7 +688,10 @@ impl RemoteServerProjects {
|
||||||
connection,
|
connection,
|
||||||
entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)),
|
entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)),
|
||||||
});
|
});
|
||||||
self.focus_handle(cx).focus(window);
|
let search_editor = self.search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,7 +826,10 @@ impl RemoteServerProjects {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
|
self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
|
||||||
self.focus_handle.focus(window);
|
let search_editor = self.search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -691,7 +849,10 @@ impl RemoteServerProjects {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
|
self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
|
||||||
self.focus_handle(cx).focus(window);
|
let search_editor = self.search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1328,7 +1489,10 @@ impl RemoteServerProjects {
|
||||||
.track_focus(&entries[3].focus_handle)
|
.track_focus(&entries[3].focus_handle)
|
||||||
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
|
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
|
||||||
this.mode = Mode::default_mode(&this.ssh_config_servers, cx);
|
this.mode = Mode::default_mode(&this.ssh_config_servers, cx);
|
||||||
cx.focus_self(window);
|
let search_editor = this.search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
|
@ -1345,7 +1509,10 @@ impl RemoteServerProjects {
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.mode =
|
this.mode =
|
||||||
Mode::default_mode(&this.ssh_config_servers, cx);
|
Mode::default_mode(&this.ssh_config_servers, cx);
|
||||||
cx.focus_self(window);
|
let search_editor = this.search_editor.clone();
|
||||||
|
cx.defer_in(window, move |_, window, cx| {
|
||||||
|
search_editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
cx.notify()
|
cx.notify()
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
@ -1449,6 +1616,11 @@ impl RemoteServerProjects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update filtered servers when query changes
|
||||||
|
let current_query = self.search_editor.read(cx).text(cx);
|
||||||
|
self.update_filtered_servers(¤t_query, cx);
|
||||||
|
let filtered_servers = self.filtered_servers.clone();
|
||||||
|
|
||||||
let scroll_state = state.scrollbar.parent_entity(&cx.entity());
|
let scroll_state = state.scrollbar.parent_entity(&cx.entity());
|
||||||
let connect_button = div()
|
let connect_button = div()
|
||||||
.id("ssh-connect-new-server-container")
|
.id("ssh-connect-new-server-container")
|
||||||
|
@ -1487,7 +1659,6 @@ impl RemoteServerProjects {
|
||||||
|
|
||||||
let mut modal_section = Navigable::new(
|
let mut modal_section = Navigable::new(
|
||||||
v_flex()
|
v_flex()
|
||||||
.track_focus(&self.focus_handle(cx))
|
|
||||||
.id("ssh-server-list")
|
.id("ssh-server-list")
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.track_scroll(scroll_handle)
|
.track_scroll(scroll_handle)
|
||||||
|
@ -1499,22 +1670,28 @@ impl RemoteServerProjects {
|
||||||
v_flex()
|
v_flex()
|
||||||
.child(
|
.child(
|
||||||
div().px_3().child(
|
div().px_3().child(
|
||||||
Label::new("No remote servers registered yet.")
|
Label::new(if current_query.trim().is_empty() {
|
||||||
.color(Color::Muted),
|
"No remote servers registered yet."
|
||||||
|
} else {
|
||||||
|
"No matching servers found."
|
||||||
|
})
|
||||||
|
.color(Color::Muted),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.children(state.servers.iter().enumerate().map(|(ix, connection)| {
|
.children(filtered_servers.iter().enumerate().map(
|
||||||
self.render_ssh_connection(ix, connection.clone(), window, cx)
|
|(ix, (server, _matches))| {
|
||||||
.into_any_element()
|
self.render_ssh_connection(ix, server.clone(), window, cx)
|
||||||
})),
|
.into_any_element()
|
||||||
|
},
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.entry(state.add_new_server.clone());
|
.entry(state.add_new_server.clone());
|
||||||
|
|
||||||
for server in &state.servers {
|
for (server, _) in &filtered_servers {
|
||||||
match server {
|
match server {
|
||||||
RemoteEntry::Project {
|
RemoteEntry::Project {
|
||||||
open_folder,
|
open_folder,
|
||||||
|
@ -1547,54 +1724,63 @@ impl RemoteServerProjects {
|
||||||
window.keystroke_text_for(&menu::Confirm),
|
window.keystroke_text_for(&menu::Confirm),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let placeholder_text = Arc::from(format!(
|
let _placeholder_text: Arc<str> = Arc::from(format!(
|
||||||
"{reuse_window} reuses this window, {create_window} opens a new one",
|
"{reuse_window} reuses this window, {create_window} opens a new one",
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Set up search editor change listener
|
||||||
|
let search_editor = self.search_editor.clone();
|
||||||
|
|
||||||
Modal::new("remote-projects", None)
|
Modal::new("remote-projects", None)
|
||||||
.header(
|
.header(
|
||||||
ModalHeader::new()
|
ModalHeader::new()
|
||||||
.child(Headline::new("Remote Projects").size(HeadlineSize::XSmall))
|
.child(Headline::new("Remote Projects").size(HeadlineSize::XSmall)),
|
||||||
.child(
|
|
||||||
Label::new(placeholder_text)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(LabelSize::XSmall),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.section(
|
.section(
|
||||||
Section::new().padded(false).child(
|
Section::new().padded(false).child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.min_h(rems(20.))
|
|
||||||
.size_full()
|
|
||||||
.relative()
|
|
||||||
.child(ListSeparator)
|
|
||||||
.child(
|
|
||||||
canvas(
|
|
||||||
|bounds, window, cx| {
|
|
||||||
modal_section.prepaint_as_root(
|
|
||||||
bounds.origin,
|
|
||||||
bounds.size.into(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
modal_section
|
|
||||||
},
|
|
||||||
|_, mut modal_section, window, cx| {
|
|
||||||
modal_section.paint(window, cx);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.size_full(),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.occlude()
|
.p_2()
|
||||||
.h_full()
|
.border_b_1()
|
||||||
.absolute()
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.top_1()
|
.track_focus(&search_editor.focus_handle(cx))
|
||||||
.bottom_1()
|
.child(search_editor.clone()),
|
||||||
.right_1()
|
)
|
||||||
.w(px(8.))
|
.child(
|
||||||
.children(Scrollbar::vertical(scroll_state)),
|
v_flex()
|
||||||
|
.min_h(rems(20.))
|
||||||
|
.size_full()
|
||||||
|
.relative()
|
||||||
|
.child(ListSeparator)
|
||||||
|
.child(
|
||||||
|
canvas(
|
||||||
|
|bounds, window, cx| {
|
||||||
|
modal_section.prepaint_as_root(
|
||||||
|
bounds.origin,
|
||||||
|
bounds.size.into(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
modal_section
|
||||||
|
},
|
||||||
|
|_, mut modal_section, window, cx| {
|
||||||
|
modal_section.paint(window, cx);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.occlude()
|
||||||
|
.h_full()
|
||||||
|
.absolute()
|
||||||
|
.top_1()
|
||||||
|
.bottom_1()
|
||||||
|
.right_1()
|
||||||
|
.w(px(8.))
|
||||||
|
.children(Scrollbar::vertical(scroll_state)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1700,7 +1886,9 @@ impl Focusable for RemoteServerProjects {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::ProjectPicker(picker) => picker.focus_handle(cx),
|
Mode::ProjectPicker(picker) => picker.focus_handle(cx),
|
||||||
_ => self.focus_handle.clone(),
|
Mode::CreateRemoteServer(state) => state.address_editor.focus_handle(cx),
|
||||||
|
Mode::EditNickname(state) => state.editor.focus_handle(cx),
|
||||||
|
_ => self.search_editor.focus_handle(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1712,11 +1900,18 @@ impl Render for RemoteServerProjects {
|
||||||
div()
|
div()
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
.w(rems(34.))
|
.w(rems(34.))
|
||||||
.key_context("RemoteServerModal")
|
|
||||||
.on_action(cx.listener(Self::cancel))
|
.on_action(cx.listener(Self::cancel))
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
this.focus_handle(cx).focus(window);
|
// Focus the appropriate element based on mode
|
||||||
|
match &this.mode {
|
||||||
|
Mode::Default(_) => {
|
||||||
|
this.search_editor.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
this.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
.on_mouse_down_out(cx.listener(|this, _, _, cx| {
|
.on_mouse_down_out(cx.listener(|this, _, _, cx| {
|
||||||
if matches!(this.mode, Mode::Default(_)) {
|
if matches!(this.mode, Mode::Default(_)) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue