Merge branch 'main' into feedback-modal-ui
This commit is contained in:
@@ -331,7 +331,7 @@ impl AssistantPanel {
|
||||
|
||||
let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
|
||||
let inline_assistant = cx.build_view(|cx| {
|
||||
let assistant = InlineAssistant::new(
|
||||
InlineAssistant::new(
|
||||
inline_assist_id,
|
||||
measurements.clone(),
|
||||
self.include_conversation_in_next_inline_assist,
|
||||
@@ -342,9 +342,7 @@ impl AssistantPanel {
|
||||
self.retrieve_context_in_next_inline_assist,
|
||||
self.semantic_index.clone(),
|
||||
project.clone(),
|
||||
);
|
||||
assistant.focus_handle.focus(cx);
|
||||
assistant
|
||||
)
|
||||
});
|
||||
let block_id = editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
@@ -391,10 +389,7 @@ impl AssistantPanel {
|
||||
if let Some(inline_assistant) = inline_assistant.upgrade() {
|
||||
if let EditorEvent::SelectionsChanged { local } = event {
|
||||
if *local
|
||||
&& inline_assistant
|
||||
.read(cx)
|
||||
.focus_handle
|
||||
.contains_focused(cx)
|
||||
&& inline_assistant.focus_handle(cx).contains_focused(cx)
|
||||
{
|
||||
cx.focus_view(&editor);
|
||||
}
|
||||
@@ -553,9 +548,12 @@ impl AssistantPanel {
|
||||
fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
|
||||
if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
|
||||
if let Some(editor) = pending_assist.editor.upgrade() {
|
||||
if let Some((block_id, _)) = pending_assist.inline_assistant.take() {
|
||||
if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
if inline_assistant.focus_handle(cx).contains_focused(cx) {
|
||||
editor.focus(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -891,8 +889,9 @@ impl AssistantPanel {
|
||||
if search_bar.show(cx) {
|
||||
search_bar.search_suggested(cx);
|
||||
if action.focus {
|
||||
let focus_handle = search_bar.focus_handle(cx);
|
||||
search_bar.select_query(cx);
|
||||
cx.focus_self();
|
||||
cx.focus(&focus_handle);
|
||||
}
|
||||
propagate = false
|
||||
}
|
||||
@@ -956,7 +955,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
IconButton::new("split_button", Icon::Menu)
|
||||
IconButton::new("split_button", Icon::SplitMessage)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
if let Some(active_editor) = this.active_editor() {
|
||||
active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
|
||||
@@ -966,7 +965,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
IconButton::new("assist_button", Icon::Menu)
|
||||
IconButton::new("assist_button", Icon::MagicWand)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
if let Some(active_editor) = this.active_editor() {
|
||||
active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
|
||||
@@ -976,7 +975,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
IconButton::new("quote_button", Icon::Menu)
|
||||
IconButton::new("quote_button", Icon::Quote)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
if let Some(workspace) = this.workspace.upgrade() {
|
||||
cx.window_context().defer(move |cx| {
|
||||
@@ -990,7 +989,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
IconButton::new("plus_button", Icon::Menu)
|
||||
IconButton::new("plus_button", Icon::Plus)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
this.new_conversation(cx);
|
||||
}))
|
||||
@@ -999,7 +998,7 @@ impl AssistantPanel {
|
||||
|
||||
fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let zoomed = self.zoomed;
|
||||
IconButton::new("zoom_button", Icon::Menu)
|
||||
IconButton::new("zoom_button", Icon::MagnifyingGlass)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
this.toggle_zoom(&ToggleZoom, cx);
|
||||
}))
|
||||
@@ -1028,6 +1027,8 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||
cx.focus(&self.focus_handle);
|
||||
|
||||
if let Some(ix) = self.editor_index_for_path(&path, cx) {
|
||||
self.set_active_editor_index(Some(ix), cx);
|
||||
return Task::ready(Ok(()));
|
||||
@@ -2026,7 +2027,6 @@ struct ConversationEditor {
|
||||
editor: View<Editor>,
|
||||
blocks: HashSet<BlockId>,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
focus_handle: FocusHandle,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -2057,13 +2057,10 @@ impl ConversationEditor {
|
||||
editor
|
||||
});
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.observe(&conversation, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&conversation, Self::handle_conversation_event),
|
||||
cx.subscribe(&editor, Self::handle_editor_event),
|
||||
cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.editor)),
|
||||
];
|
||||
|
||||
let mut this = Self {
|
||||
@@ -2073,7 +2070,6 @@ impl ConversationEditor {
|
||||
scroll_position: None,
|
||||
fs,
|
||||
workspace,
|
||||
focus_handle,
|
||||
_subscriptions,
|
||||
};
|
||||
this.update_message_headers(cx);
|
||||
@@ -2484,8 +2480,8 @@ impl Render for ConversationEditor {
|
||||
}
|
||||
|
||||
impl FocusableView for ConversationEditor {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2539,7 +2535,6 @@ struct InlineAssistant {
|
||||
prompt_editor: View<Editor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
confirmed: bool,
|
||||
focus_handle: FocusHandle,
|
||||
include_conversation: bool,
|
||||
measurements: Rc<Cell<BlockMeasurements>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
@@ -2635,8 +2630,8 @@ impl Render for InlineAssistant {
|
||||
}
|
||||
|
||||
impl FocusableView for InlineAssistant {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.prompt_editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2662,12 +2657,11 @@ impl InlineAssistant {
|
||||
editor.set_placeholder_text(placeholder, cx);
|
||||
editor
|
||||
});
|
||||
cx.focus_view(&prompt_editor);
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
let mut subscriptions = vec![
|
||||
cx.observe(&codegen, Self::handle_codegen_changed),
|
||||
cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
|
||||
cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.prompt_editor)),
|
||||
];
|
||||
|
||||
if let Some(semantic_index) = semantic_index.clone() {
|
||||
@@ -2679,7 +2673,6 @@ impl InlineAssistant {
|
||||
prompt_editor,
|
||||
workspace,
|
||||
confirmed: false,
|
||||
focus_handle,
|
||||
include_conversation,
|
||||
measurements,
|
||||
prompt_history,
|
||||
|
||||
@@ -1814,34 +1814,34 @@ impl Editor {
|
||||
this
|
||||
}
|
||||
|
||||
fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
|
||||
let mut dispatch_context = KeyContext::default();
|
||||
dispatch_context.add("Editor");
|
||||
fn key_context(&self, cx: &AppContext) -> KeyContext {
|
||||
let mut key_context = KeyContext::default();
|
||||
key_context.add("Editor");
|
||||
let mode = match self.mode {
|
||||
EditorMode::SingleLine => "single_line",
|
||||
EditorMode::AutoHeight { .. } => "auto_height",
|
||||
EditorMode::Full => "full",
|
||||
};
|
||||
dispatch_context.set("mode", mode);
|
||||
key_context.set("mode", mode);
|
||||
if self.pending_rename.is_some() {
|
||||
dispatch_context.add("renaming");
|
||||
key_context.add("renaming");
|
||||
}
|
||||
if self.context_menu_visible() {
|
||||
match self.context_menu.read().as_ref() {
|
||||
Some(ContextMenu::Completions(_)) => {
|
||||
dispatch_context.add("menu");
|
||||
dispatch_context.add("showing_completions")
|
||||
key_context.add("menu");
|
||||
key_context.add("showing_completions")
|
||||
}
|
||||
Some(ContextMenu::CodeActions(_)) => {
|
||||
dispatch_context.add("menu");
|
||||
dispatch_context.add("showing_code_actions")
|
||||
key_context.add("menu");
|
||||
key_context.add("showing_code_actions")
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
for layer in self.keymap_context_layers.values() {
|
||||
dispatch_context.extend(layer);
|
||||
key_context.extend(layer);
|
||||
}
|
||||
|
||||
if let Some(extension) = self
|
||||
@@ -1850,10 +1850,10 @@ impl Editor {
|
||||
.as_singleton()
|
||||
.and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
|
||||
{
|
||||
dispatch_context.set("extension", extension.to_string());
|
||||
key_context.set("extension", extension.to_string());
|
||||
}
|
||||
|
||||
dispatch_context
|
||||
key_context
|
||||
}
|
||||
|
||||
pub fn new_file(
|
||||
|
||||
@@ -275,36 +275,48 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::copy_relative_path);
|
||||
register_action(view, cx, Editor::copy_highlight_json);
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.format(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
if let Some(task) = editor.format(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(view, cx, Editor::restart_language_server);
|
||||
register_action(view, cx, Editor::show_character_palette);
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.confirm_completion(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
if let Some(task) = editor.confirm_completion(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.confirm_code_action(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
if let Some(task) = editor.confirm_code_action(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.rename(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
if let Some(task) = editor.rename(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.confirm_rename(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
if let Some(task) = editor.confirm_rename(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.find_all_references(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
if let Some(task) = editor.find_all_references(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(view, cx, Editor::next_copilot_suggestion);
|
||||
register_action(view, cx, Editor::previous_copilot_suggestion);
|
||||
@@ -2802,49 +2814,38 @@ impl Element for EditorElement {
|
||||
};
|
||||
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
let dispatch_context = self.editor.read(cx).dispatch_context(cx);
|
||||
cx.with_key_dispatch(
|
||||
Some(dispatch_context),
|
||||
Some(focus_handle.clone()),
|
||||
|_, cx| {
|
||||
self.register_actions(cx);
|
||||
self.register_key_listeners(cx);
|
||||
let key_context = self.editor.read(cx).key_context(cx);
|
||||
cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| {
|
||||
self.register_actions(cx);
|
||||
self.register_key_listeners(cx);
|
||||
|
||||
// We call with_z_index to establish a new stacking context.
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
// Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
|
||||
// take precedence.
|
||||
cx.with_z_index(0, |cx| {
|
||||
self.paint_mouse_listeners(
|
||||
bounds,
|
||||
gutter_bounds,
|
||||
text_bounds,
|
||||
&layout,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let input_handler =
|
||||
ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
||||
cx.handle_input(&focus_handle, input_handler);
|
||||
|
||||
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
|
||||
if layout.gutter_size.width > Pixels::ZERO {
|
||||
self.paint_gutter(gutter_bounds, &mut layout, cx);
|
||||
}
|
||||
self.paint_text(text_bounds, &mut layout, cx);
|
||||
|
||||
if !layout.blocks.is_empty() {
|
||||
cx.with_z_index(1, |cx| {
|
||||
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||
self.paint_blocks(bounds, &mut layout, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
// We call with_z_index to establish a new stacking context.
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
// Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
|
||||
// take precedence.
|
||||
cx.with_z_index(0, |cx| {
|
||||
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
|
||||
});
|
||||
let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
||||
cx.handle_input(&focus_handle, input_handler);
|
||||
|
||||
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
|
||||
if layout.gutter_size.width > Pixels::ZERO {
|
||||
self.paint_gutter(gutter_bounds, &mut layout, cx);
|
||||
}
|
||||
self.paint_text(text_bounds, &mut layout, cx);
|
||||
|
||||
if !layout.blocks.is_empty() {
|
||||
cx.with_z_index(1, |cx| {
|
||||
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||
self.paint_blocks(bounds, &mut layout, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-5
@@ -704,14 +704,13 @@ impl AppContext {
|
||||
let focus_changed = focused.is_some() || blurred.is_some();
|
||||
let event = FocusEvent { focused, blurred };
|
||||
|
||||
let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners);
|
||||
let mut listeners = mem::take(&mut cx.window.rendered_frame.focus_listeners);
|
||||
if focus_changed {
|
||||
for listener in &mut listeners {
|
||||
listener(&event, cx);
|
||||
}
|
||||
}
|
||||
listeners.extend(cx.window.current_frame.focus_listeners.drain(..));
|
||||
cx.window.current_frame.focus_listeners = listeners;
|
||||
cx.window.rendered_frame.focus_listeners = listeners;
|
||||
|
||||
if focus_changed {
|
||||
cx.window
|
||||
@@ -1029,9 +1028,13 @@ impl AppContext {
|
||||
window
|
||||
.update(self, |_, cx| {
|
||||
cx.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.clear_pending_keystrokes()
|
||||
.clear_pending_keystrokes();
|
||||
cx.window
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.clear_pending_keystrokes();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -1107,6 +1110,10 @@ impl AppContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_active_drag(&self) -> bool {
|
||||
self.active_drag.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
|
||||
@@ -69,24 +69,6 @@ pub trait IntoElement: Sized {
|
||||
self.map(|this| if condition { then(this) } else { this })
|
||||
}
|
||||
|
||||
fn when_else(
|
||||
self,
|
||||
condition: bool,
|
||||
then: impl FnOnce(Self) -> Self,
|
||||
otherwise: impl FnOnce(Self) -> Self,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.map(|this| {
|
||||
if condition {
|
||||
then(this)
|
||||
} else {
|
||||
otherwise(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
@@ -193,6 +193,12 @@ impl Deref for MouseExitEvent {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
||||
|
||||
impl ExternalPaths {
|
||||
pub fn paths(&self) -> &[PathBuf] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ExternalPaths {
|
||||
type Element = Div;
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ impl MetalRenderer {
|
||||
.entry(tile.texture_id)
|
||||
.or_insert(Vec::new())
|
||||
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
||||
xy_position: vertex.xy_position - path.bounds.origin
|
||||
xy_position: vertex.xy_position - clipped_bounds.origin
|
||||
+ tile.bounds.origin.map(Into::into),
|
||||
st_position: vertex.st_position,
|
||||
content_mask: ContentMask {
|
||||
@@ -544,9 +544,10 @@ impl MetalRenderer {
|
||||
if let Some((path, tile)) = paths_and_tiles.peek() {
|
||||
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
|
||||
prev_texture_id = Some(tile.texture_id);
|
||||
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
|
||||
sprites.push(PathSprite {
|
||||
bounds: Bounds {
|
||||
origin: path.bounds.origin.map(|p| p.floor()),
|
||||
origin: origin.map(|p| p.floor()),
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
},
|
||||
color: path.color,
|
||||
|
||||
+121
-145
@@ -4,12 +4,12 @@ use crate::{
|
||||
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
|
||||
EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
|
||||
ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
|
||||
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
|
||||
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
||||
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
|
||||
Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
|
||||
UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
|
||||
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
|
||||
Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
|
||||
WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::HashMap;
|
||||
@@ -90,7 +90,7 @@ impl FocusId {
|
||||
/// Obtains whether this handle contains the given handle in the most recently rendered frame.
|
||||
pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
|
||||
cx.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focus_contains(*self, other)
|
||||
}
|
||||
@@ -212,8 +212,8 @@ pub struct Window {
|
||||
layout_engine: Option<TaffyLayoutEngine>,
|
||||
pub(crate) root_view: Option<AnyView>,
|
||||
pub(crate) element_id_stack: GlobalElementId,
|
||||
pub(crate) previous_frame: Frame,
|
||||
pub(crate) current_frame: Frame,
|
||||
pub(crate) rendered_frame: Frame,
|
||||
pub(crate) next_frame: Frame,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
||||
default_prevented: bool,
|
||||
@@ -249,7 +249,7 @@ pub(crate) struct Frame {
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn new(dispatch_tree: DispatchTree) -> Self {
|
||||
fn new(dispatch_tree: DispatchTree) -> Self {
|
||||
Frame {
|
||||
element_states: HashMap::default(),
|
||||
mouse_listeners: HashMap::default(),
|
||||
@@ -262,6 +262,14 @@ impl Frame {
|
||||
element_offset_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.element_states.clear();
|
||||
self.mouse_listeners.values_mut().for_each(Vec::clear);
|
||||
self.focus_listeners.clear();
|
||||
self.dispatch_tree.clear();
|
||||
self.depth_map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -330,8 +338,8 @@ impl Window {
|
||||
layout_engine: Some(TaffyLayoutEngine::new()),
|
||||
root_view: None,
|
||||
element_id_stack: GlobalElementId::default(),
|
||||
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
current_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
focus_listeners: SubscriberSet::new(),
|
||||
default_prevented: true,
|
||||
@@ -428,7 +436,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
self.window.focus = Some(focus_id);
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.clear_pending_keystrokes();
|
||||
self.app.push_effect(Effect::FocusChanged {
|
||||
@@ -459,11 +467,11 @@ impl<'a> WindowContext<'a> {
|
||||
let node_id = focus_handle
|
||||
.and_then(|handle| {
|
||||
cx.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(handle.id)
|
||||
})
|
||||
.unwrap_or_else(|| cx.window.current_frame.dispatch_tree.root_node_id());
|
||||
.unwrap_or_else(|| cx.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
|
||||
cx.propagate_event = true;
|
||||
cx.dispatch_action_on_node(node_id, action);
|
||||
@@ -743,7 +751,7 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.default_prevented
|
||||
}
|
||||
|
||||
/// Register a mouse event listener on the window for the current frame. The type of event
|
||||
/// Register a mouse event listener on the window for the next frame. The type of event
|
||||
/// is determined by the first parameter of the given listener. When the next frame is rendered
|
||||
/// the listener will be cleared.
|
||||
///
|
||||
@@ -753,9 +761,9 @@ impl<'a> WindowContext<'a> {
|
||||
&mut self,
|
||||
handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
|
||||
) {
|
||||
let order = self.window.current_frame.z_index_stack.clone();
|
||||
let order = self.window.next_frame.z_index_stack.clone();
|
||||
self.window
|
||||
.current_frame
|
||||
.next_frame
|
||||
.mouse_listeners
|
||||
.entry(TypeId::of::<Event>())
|
||||
.or_default()
|
||||
@@ -767,7 +775,7 @@ impl<'a> WindowContext<'a> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Register a key event listener on the window for the current frame. The type of event
|
||||
/// Register a key event listener on the window for the next frame. The type of event
|
||||
/// is determined by the first parameter of the given listener. When the next frame is rendered
|
||||
/// the listener will be cleared.
|
||||
///
|
||||
@@ -778,7 +786,7 @@ impl<'a> WindowContext<'a> {
|
||||
handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
|
||||
) {
|
||||
self.window
|
||||
.current_frame
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.on_key_event(Rc::new(move |event, phase, cx| {
|
||||
if let Some(event) = event.downcast_ref::<Event>() {
|
||||
@@ -787,7 +795,7 @@ impl<'a> WindowContext<'a> {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Register an action listener on the window for the current frame. The type of action
|
||||
/// Register an action listener on the window for the next frame. The type of action
|
||||
/// is determined by the first parameter of the given listener. When the next frame is rendered
|
||||
/// the listener will be cleared.
|
||||
///
|
||||
@@ -798,7 +806,7 @@ impl<'a> WindowContext<'a> {
|
||||
action_type: TypeId,
|
||||
handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
|
||||
) {
|
||||
self.window.current_frame.dispatch_tree.on_action(
|
||||
self.window.next_frame.dispatch_tree.on_action(
|
||||
action_type,
|
||||
Rc::new(move |action, phase, cx| handler(action, phase, cx)),
|
||||
);
|
||||
@@ -809,13 +817,13 @@ impl<'a> WindowContext<'a> {
|
||||
.focused()
|
||||
.and_then(|focused_handle| {
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focused_handle.id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.is_action_available(action, target)
|
||||
}
|
||||
@@ -832,16 +840,16 @@ impl<'a> WindowContext<'a> {
|
||||
/// Called during painting to invoke the given closure in a new stacking context. The given
|
||||
/// z-index is interpreted relative to the previous call to `stack`.
|
||||
pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.window.current_frame.z_index_stack.push(z_index);
|
||||
self.window.next_frame.z_index_stack.push(z_index);
|
||||
let result = f(self);
|
||||
self.window.current_frame.z_index_stack.pop();
|
||||
self.window.next_frame.z_index_stack.pop();
|
||||
result
|
||||
}
|
||||
|
||||
/// Called during painting to track which z-index is on top at each pixel position
|
||||
pub fn add_opaque_layer(&mut self, bounds: Bounds<Pixels>) {
|
||||
let stacking_order = self.window.current_frame.z_index_stack.clone();
|
||||
let depth_map = &mut self.window.current_frame.depth_map;
|
||||
let stacking_order = self.window.next_frame.z_index_stack.clone();
|
||||
let depth_map = &mut self.window.next_frame.depth_map;
|
||||
match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(&level)) {
|
||||
Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)),
|
||||
}
|
||||
@@ -850,7 +858,7 @@ impl<'a> WindowContext<'a> {
|
||||
/// Returns true if the top-most opaque layer painted over this point was part of the
|
||||
/// same layer as the given stacking order.
|
||||
pub fn was_top_layer(&self, point: &Point<Pixels>, level: &StackingOrder) -> bool {
|
||||
for (stack, bounds) in self.window.previous_frame.depth_map.iter() {
|
||||
for (stack, bounds) in self.window.rendered_frame.depth_map.iter() {
|
||||
if bounds.contains_point(point) {
|
||||
return level.starts_with(stack) || stack.starts_with(level);
|
||||
}
|
||||
@@ -861,10 +869,10 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Called during painting to get the current stacking order.
|
||||
pub fn stacking_order(&self) -> &StackingOrder {
|
||||
&self.window.current_frame.z_index_stack
|
||||
&self.window.next_frame.z_index_stack
|
||||
}
|
||||
|
||||
/// Paint one or more drop shadows into the scene for the current frame at the current z-index.
|
||||
/// Paint one or more drop shadows into the scene for the next frame at the current z-index.
|
||||
pub fn paint_shadows(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
@@ -878,8 +886,8 @@ impl<'a> WindowContext<'a> {
|
||||
let mut shadow_bounds = bounds;
|
||||
shadow_bounds.origin += shadow.offset;
|
||||
shadow_bounds.dilate(shadow.spread_radius);
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
Shadow {
|
||||
order: 0,
|
||||
bounds: shadow_bounds.scale(scale_factor),
|
||||
@@ -892,7 +900,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Paint one or more quads into the scene for the current frame at the current stacking context.
|
||||
/// Paint one or more quads into the scene for the next frame at the current stacking context.
|
||||
/// Quads are colored rectangular regions with an optional background, border, and corner radius.
|
||||
pub fn paint_quad(
|
||||
&mut self,
|
||||
@@ -906,8 +914,8 @@ impl<'a> WindowContext<'a> {
|
||||
let content_mask = self.content_mask();
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
Quad {
|
||||
order: 0,
|
||||
bounds: bounds.scale(scale_factor),
|
||||
@@ -920,20 +928,20 @@ impl<'a> WindowContext<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Paint the given `Path` into the scene for the current frame at the current z-index.
|
||||
/// Paint the given `Path` into the scene for the next frame at the current z-index.
|
||||
pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let content_mask = self.content_mask();
|
||||
path.content_mask = content_mask;
|
||||
path.color = color.into();
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
path.scale(scale_factor),
|
||||
);
|
||||
window
|
||||
.next_frame
|
||||
.scene_builder
|
||||
.insert(&window.next_frame.z_index_stack, path.scale(scale_factor));
|
||||
}
|
||||
|
||||
/// Paint an underline into the scene for the current frame at the current z-index.
|
||||
/// Paint an underline into the scene for the next frame at the current z-index.
|
||||
pub fn paint_underline(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
@@ -952,8 +960,8 @@ impl<'a> WindowContext<'a> {
|
||||
};
|
||||
let content_mask = self.content_mask();
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
Underline {
|
||||
order: 0,
|
||||
bounds: bounds.scale(scale_factor),
|
||||
@@ -965,7 +973,7 @@ impl<'a> WindowContext<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
|
||||
/// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index.
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
pub fn paint_glyph(
|
||||
&mut self,
|
||||
@@ -1005,8 +1013,8 @@ impl<'a> WindowContext<'a> {
|
||||
};
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
MonochromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -1019,7 +1027,7 @@ impl<'a> WindowContext<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint an emoji glyph into the scene for the current frame at the current z-index.
|
||||
/// Paint an emoji glyph into the scene for the next frame at the current z-index.
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
pub fn paint_emoji(
|
||||
&mut self,
|
||||
@@ -1056,8 +1064,8 @@ impl<'a> WindowContext<'a> {
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let window = &mut *self.window;
|
||||
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
PolychromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -1071,7 +1079,7 @@ impl<'a> WindowContext<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint a monochrome SVG into the scene for the current frame at the current stacking context.
|
||||
/// Paint a monochrome SVG into the scene for the next frame at the current stacking context.
|
||||
pub fn paint_svg(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
@@ -1098,8 +1106,8 @@ impl<'a> WindowContext<'a> {
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
MonochromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -1112,7 +1120,7 @@ impl<'a> WindowContext<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint an image into the scene for the current frame at the current z-index.
|
||||
/// Paint an image into the scene for the next frame at the current z-index.
|
||||
pub fn paint_image(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
@@ -1134,8 +1142,8 @@ impl<'a> WindowContext<'a> {
|
||||
let corner_radii = corner_radii.scale(scale_factor);
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
PolychromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -1148,14 +1156,14 @@ impl<'a> WindowContext<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint a surface into the scene for the current frame at the current z-index.
|
||||
/// Paint a surface into the scene for the next frame at the current z-index.
|
||||
pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let bounds = bounds.scale(scale_factor);
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
window.next_frame.scene_builder.insert(
|
||||
&window.next_frame.z_index_stack,
|
||||
Surface {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -1167,15 +1175,17 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Draw pixels to the display for this window based on the contents of its scene.
|
||||
pub(crate) fn draw(&mut self) {
|
||||
self.text_system().start_frame();
|
||||
self.window.platform_window.clear_input_handler();
|
||||
self.window.layout_engine.as_mut().unwrap().clear();
|
||||
self.window.next_frame.clear();
|
||||
let root_view = self.window.root_view.take().unwrap();
|
||||
|
||||
self.start_frame();
|
||||
|
||||
self.with_z_index(0, |cx| {
|
||||
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
|
||||
for (action_type, action_listeners) in &cx.app.global_action_listeners {
|
||||
for action_listener in action_listeners.iter().cloned() {
|
||||
cx.window.current_frame.dispatch_tree.on_action(
|
||||
cx.window.next_frame.dispatch_tree.on_action(
|
||||
*action_type,
|
||||
Rc::new(move |action, phase, cx| action_listener(action, phase, cx)),
|
||||
)
|
||||
@@ -1204,16 +1214,18 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
self.window
|
||||
.current_frame
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.preserve_pending_keystrokes(
|
||||
&mut self.window.previous_frame.dispatch_tree,
|
||||
&mut self.window.rendered_frame.dispatch_tree,
|
||||
self.window.focus,
|
||||
);
|
||||
|
||||
self.window.root_view = Some(root_view);
|
||||
let scene = self.window.current_frame.scene_builder.build();
|
||||
|
||||
let window = &mut self.window;
|
||||
mem::swap(&mut window.rendered_frame, &mut window.next_frame);
|
||||
|
||||
let scene = self.window.rendered_frame.scene_builder.build();
|
||||
self.window.platform_window.draw(scene);
|
||||
let cursor_style = self
|
||||
.window
|
||||
@@ -1225,24 +1237,6 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.dirty = false;
|
||||
}
|
||||
|
||||
/// Rotate the current frame and the previous frame, then clear the current frame.
|
||||
/// We repopulate all state in the current frame during each paint.
|
||||
fn start_frame(&mut self) {
|
||||
self.window.platform_window.clear_input_handler();
|
||||
self.text_system().start_frame();
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.layout_engine.as_mut().unwrap().clear();
|
||||
|
||||
mem::swap(&mut window.previous_frame, &mut window.current_frame);
|
||||
let frame = &mut window.current_frame;
|
||||
frame.element_states.clear();
|
||||
frame.mouse_listeners.values_mut().for_each(Vec::clear);
|
||||
frame.focus_listeners.clear();
|
||||
frame.dispatch_tree.clear();
|
||||
frame.depth_map.clear();
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
pub fn dispatch_event(&mut self, event: InputEvent) -> bool {
|
||||
// Handlers may set this to false by calling `stop_propagation`
|
||||
@@ -1275,10 +1269,9 @@ impl<'a> WindowContext<'a> {
|
||||
cursor_offset: position,
|
||||
});
|
||||
}
|
||||
InputEvent::MouseDown(MouseDownEvent {
|
||||
InputEvent::MouseMove(MouseMoveEvent {
|
||||
position,
|
||||
button: MouseButton::Left,
|
||||
click_count: 1,
|
||||
pressed_button: Some(MouseButton::Left),
|
||||
modifiers: Modifiers::default(),
|
||||
})
|
||||
}
|
||||
@@ -1291,6 +1284,7 @@ impl<'a> WindowContext<'a> {
|
||||
})
|
||||
}
|
||||
FileDropEvent::Submit { position } => {
|
||||
self.activate(true);
|
||||
self.window.mouse_position = position;
|
||||
InputEvent::MouseUp(MouseUpEvent {
|
||||
button: MouseButton::Left,
|
||||
@@ -1321,7 +1315,7 @@ impl<'a> WindowContext<'a> {
|
||||
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
||||
if let Some(mut handlers) = self
|
||||
.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.mouse_listeners
|
||||
.remove(&event.type_id())
|
||||
{
|
||||
@@ -1351,17 +1345,8 @@ impl<'a> WindowContext<'a> {
|
||||
self.active_drag = None;
|
||||
}
|
||||
|
||||
// Just in case any handlers added new handlers, which is weird, but possible.
|
||||
handlers.extend(
|
||||
self.window
|
||||
.current_frame
|
||||
.mouse_listeners
|
||||
.get_mut(&event.type_id())
|
||||
.into_iter()
|
||||
.flat_map(|handlers| handlers.drain(..)),
|
||||
);
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.mouse_listeners
|
||||
.insert(event.type_id(), handlers);
|
||||
}
|
||||
@@ -1373,15 +1358,15 @@ impl<'a> WindowContext<'a> {
|
||||
.focus
|
||||
.and_then(|focus_id| {
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focus_id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
|
||||
let dispatch_path = self
|
||||
.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_path(node_id);
|
||||
|
||||
@@ -1392,7 +1377,7 @@ impl<'a> WindowContext<'a> {
|
||||
self.propagate_event = true;
|
||||
|
||||
for node_id in &dispatch_path {
|
||||
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
|
||||
if let Some(context) = node.context.clone() {
|
||||
context_stack.push(context);
|
||||
@@ -1409,7 +1394,7 @@ impl<'a> WindowContext<'a> {
|
||||
// Bubble phase
|
||||
for node_id in dispatch_path.iter().rev() {
|
||||
// Handle low level key events
|
||||
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
for key_listener in node.key_listeners.clone() {
|
||||
key_listener(event, DispatchPhase::Bubble, self);
|
||||
if !self.propagate_event {
|
||||
@@ -1418,12 +1403,12 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
// Match keystrokes
|
||||
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
if node.context.is_some() {
|
||||
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
if let Some(found) = self
|
||||
.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_key(&key_down_event.keystroke, &context_stack)
|
||||
{
|
||||
@@ -1446,13 +1431,13 @@ impl<'a> WindowContext<'a> {
|
||||
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
|
||||
let dispatch_path = self
|
||||
.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_path(node_id);
|
||||
|
||||
// Capture phase
|
||||
for node_id in &dispatch_path {
|
||||
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
for DispatchActionListener {
|
||||
action_type,
|
||||
listener,
|
||||
@@ -1469,7 +1454,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
// Bubble phase
|
||||
for node_id in dispatch_path.iter().rev() {
|
||||
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
for DispatchActionListener {
|
||||
action_type,
|
||||
listener,
|
||||
@@ -1529,25 +1514,25 @@ impl<'a> WindowContext<'a> {
|
||||
.focus
|
||||
.and_then(|focus_id| {
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focus_id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
|
||||
self.window
|
||||
.current_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.available_actions(node_id)
|
||||
}
|
||||
|
||||
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
|
||||
self.window
|
||||
.previous_frame
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.bindings_for_action(
|
||||
action,
|
||||
&self.window.previous_frame.dispatch_tree.context_stack,
|
||||
&self.window.rendered_frame.dispatch_tree.context_stack,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1556,7 +1541,7 @@ impl<'a> WindowContext<'a> {
|
||||
action: &dyn Action,
|
||||
focus_handle: &FocusHandle,
|
||||
) -> Vec<KeyBinding> {
|
||||
let dispatch_tree = &self.window.previous_frame.dispatch_tree;
|
||||
let dispatch_tree = &self.window.rendered_frame.dispatch_tree;
|
||||
|
||||
let Some(node_id) = dispatch_tree.focusable_node_id(focus_handle.id) else {
|
||||
return vec![];
|
||||
@@ -1599,24 +1584,21 @@ impl<'a> WindowContext<'a> {
|
||||
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
|
||||
) -> R {
|
||||
let window = &mut self.window;
|
||||
window
|
||||
.current_frame
|
||||
.dispatch_tree
|
||||
.push_node(context.clone());
|
||||
window.next_frame.dispatch_tree.push_node(context.clone());
|
||||
if let Some(focus_handle) = focus_handle.as_ref() {
|
||||
window
|
||||
.current_frame
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.make_focusable(focus_handle.id);
|
||||
}
|
||||
let result = f(focus_handle, self);
|
||||
|
||||
self.window.current_frame.dispatch_tree.pop_node();
|
||||
self.window.next_frame.dispatch_tree.pop_node();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Register a focus listener for the current frame only. It will be cleared
|
||||
/// Register a focus listener for the next frame only. It will be cleared
|
||||
/// on the next frame render. You should use this method only from within elements,
|
||||
/// and we may want to enforce that better via a different context type.
|
||||
// todo!() Move this to `FrameContext` to emphasize its individuality?
|
||||
@@ -1625,7 +1607,7 @@ impl<'a> WindowContext<'a> {
|
||||
listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static,
|
||||
) {
|
||||
self.window
|
||||
.current_frame
|
||||
.next_frame
|
||||
.focus_listeners
|
||||
.push(Box::new(move |event, cx| {
|
||||
listener(event, cx);
|
||||
@@ -1876,12 +1858,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
) -> R {
|
||||
if let Some(mask) = mask {
|
||||
let mask = mask.intersect(&self.content_mask());
|
||||
self.window_mut()
|
||||
.current_frame
|
||||
.content_mask_stack
|
||||
.push(mask);
|
||||
self.window_mut().next_frame.content_mask_stack.push(mask);
|
||||
let result = f(self);
|
||||
self.window_mut().current_frame.content_mask_stack.pop();
|
||||
self.window_mut().next_frame.content_mask_stack.pop();
|
||||
result
|
||||
} else {
|
||||
f(self)
|
||||
@@ -1897,12 +1876,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
size: self.window().viewport_size,
|
||||
},
|
||||
};
|
||||
self.window_mut()
|
||||
.current_frame
|
||||
.content_mask_stack
|
||||
.push(mask);
|
||||
self.window_mut().next_frame.content_mask_stack.push(mask);
|
||||
let result = f(self);
|
||||
self.window_mut().current_frame.content_mask_stack.pop();
|
||||
self.window_mut().next_frame.content_mask_stack.pop();
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1929,26 +1905,26 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
f: impl FnOnce(&mut Self) -> R,
|
||||
) -> R {
|
||||
self.window_mut()
|
||||
.current_frame
|
||||
.next_frame
|
||||
.element_offset_stack
|
||||
.push(offset);
|
||||
let result = f(self);
|
||||
self.window_mut().current_frame.element_offset_stack.pop();
|
||||
self.window_mut().next_frame.element_offset_stack.pop();
|
||||
result
|
||||
}
|
||||
|
||||
/// Obtain the current element offset.
|
||||
fn element_offset(&self) -> Point<Pixels> {
|
||||
self.window()
|
||||
.current_frame
|
||||
.next_frame
|
||||
.element_offset_stack
|
||||
.last()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Update or intialize state for an element with the given id that lives across multiple
|
||||
/// frames. If an element with this id existed in the previous frame, its state will be passed
|
||||
/// Update or initialize state for an element with the given id that lives across multiple
|
||||
/// frames. If an element with this id existed in the rendered frame, its state will be passed
|
||||
/// to the given closure. The state returned by the closure will be stored so it can be referenced
|
||||
/// when drawing the next frame.
|
||||
fn with_element_state<S, R>(
|
||||
@@ -1964,12 +1940,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
|
||||
if let Some(any) = cx
|
||||
.window_mut()
|
||||
.current_frame
|
||||
.next_frame
|
||||
.element_states
|
||||
.remove(&global_id)
|
||||
.or_else(|| {
|
||||
cx.window_mut()
|
||||
.previous_frame
|
||||
.rendered_frame
|
||||
.element_states
|
||||
.remove(&global_id)
|
||||
})
|
||||
@@ -2011,7 +1987,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
let (result, state) = f(Some(state), cx);
|
||||
state_box.replace(state);
|
||||
cx.window_mut()
|
||||
.current_frame
|
||||
.next_frame
|
||||
.element_states
|
||||
.insert(global_id, ElementStateBox {
|
||||
inner: state_box,
|
||||
@@ -2023,7 +1999,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
} else {
|
||||
let (result, state) = f(None, cx);
|
||||
cx.window_mut()
|
||||
.current_frame
|
||||
.next_frame
|
||||
.element_states
|
||||
.insert(global_id,
|
||||
ElementStateBox {
|
||||
@@ -2042,7 +2018,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||
/// Obtain the current content mask.
|
||||
fn content_mask(&self) -> ContentMask<Pixels> {
|
||||
self.window()
|
||||
.current_frame
|
||||
.next_frame
|
||||
.content_mask_stack
|
||||
.last()
|
||||
.cloned()
|
||||
@@ -2130,9 +2106,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
}
|
||||
|
||||
pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.window.current_frame.z_index_stack.push(z_index);
|
||||
self.window.next_frame.z_index_stack.push(z_index);
|
||||
let result = f(self);
|
||||
self.window.current_frame.z_index_stack.pop();
|
||||
self.window.next_frame.z_index_stack.pop();
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ pub fn action(input: TokenStream) -> TokenStream {
|
||||
|
||||
let build_impl = if is_unit_struct {
|
||||
quote! {
|
||||
let _ = value;
|
||||
Ok(std::boxed::Box::new(Self {}))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ use gpui::{
|
||||
Action, ClickEvent, Div, ElementId, EventEmitter, InteractiveElement, ParentElement, Render,
|
||||
Stateful, Styled, Subscription, View, ViewContext, WeakView,
|
||||
};
|
||||
use search::BufferSearchBar;
|
||||
use search::{buffer_search, BufferSearchBar};
|
||||
use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
|
||||
use workspace::{
|
||||
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
@@ -64,12 +64,14 @@ impl Render for QuickActionBar {
|
||||
"toggle buffer search",
|
||||
Icon::MagnifyingGlass,
|
||||
!self.buffer_search_bar.read(cx).is_dismissed(),
|
||||
Box::new(search::buffer_search::Deploy { focus: false }),
|
||||
Box::new(buffer_search::Deploy { focus: false }),
|
||||
"Buffer Search",
|
||||
{
|
||||
let buffer_search_bar = self.buffer_search_bar.clone();
|
||||
move |_, cx| {
|
||||
buffer_search_bar.update(cx, |search_bar, cx| search_bar.toggle(cx));
|
||||
buffer_search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.toggle(&buffer_search::Deploy { focus: true }, cx)
|
||||
});
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
gpui::actions!(OpenRecent);
|
||||
@@ -1,9 +1,10 @@
|
||||
mod highlighted_workspace_location;
|
||||
mod projects;
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task,
|
||||
View, ViewContext, WeakView,
|
||||
AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
||||
use ordered_float::OrderedFloat;
|
||||
@@ -16,7 +17,7 @@ use workspace::{
|
||||
WORKSPACE_DB,
|
||||
};
|
||||
|
||||
actions!(OpenRecent);
|
||||
pub use projects::OpenRecent;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(RecentProjects::register).detach();
|
||||
|
||||
@@ -170,14 +170,25 @@ impl Render for BufferSearchBar {
|
||||
|
||||
h_stack()
|
||||
.key_context("BufferSearchBar")
|
||||
.when(in_replace, |this| {
|
||||
this.key_context("in_replace")
|
||||
.on_action(cx.listener(Self::replace_next))
|
||||
.on_action(cx.listener(Self::replace_all))
|
||||
})
|
||||
.on_action(cx.listener(Self::previous_history_query))
|
||||
.on_action(cx.listener(Self::next_history_query))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::select_next_match))
|
||||
.on_action(cx.listener(Self::select_prev_match))
|
||||
.when(self.supported_options().replacement, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_replace))
|
||||
.when(in_replace, |this| {
|
||||
this.key_context("in_replace")
|
||||
.on_action(cx.listener(Self::replace_next))
|
||||
.on_action(cx.listener(Self::replace_all))
|
||||
})
|
||||
})
|
||||
.when(self.supported_options().case, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_case_sensitive))
|
||||
})
|
||||
.when(self.supported_options().word, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_whole_word))
|
||||
})
|
||||
.w_full()
|
||||
.p_1()
|
||||
.child(
|
||||
@@ -305,7 +316,7 @@ impl BufferSearchBar {
|
||||
|
||||
let handle = cx.view().downgrade();
|
||||
|
||||
editor.register_action(move |a: &Deploy, cx| {
|
||||
editor.register_action(move |deploy: &Deploy, cx| {
|
||||
let Some(pane) = handle.upgrade().and_then(|editor| editor.read(cx).pane(cx)) else {
|
||||
return;
|
||||
};
|
||||
@@ -313,12 +324,12 @@ impl BufferSearchBar {
|
||||
pane.update(cx, |this, cx| {
|
||||
this.toolbar().update(cx, |this, cx| {
|
||||
if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |this, cx| this.toggle(cx));
|
||||
search_bar.update(cx, |this, cx| this.toggle(deploy, cx));
|
||||
return;
|
||||
}
|
||||
let view = cx.build_view(|cx| BufferSearchBar::new(cx));
|
||||
this.add_item(view.clone(), cx);
|
||||
view.update(cx, |this, cx| this.deploy(a, cx));
|
||||
view.update(cx, |this, cx| this.deploy(deploy, cx));
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
@@ -468,7 +479,7 @@ impl BufferSearchBar {
|
||||
self.search_suggested(cx);
|
||||
if deploy.focus {
|
||||
self.select_query(cx);
|
||||
let handle = cx.focus_handle();
|
||||
let handle = self.query_editor.focus_handle(cx);
|
||||
cx.focus(&handle);
|
||||
}
|
||||
return true;
|
||||
@@ -477,9 +488,9 @@ impl BufferSearchBar {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn toggle(&mut self, action: &Deploy, cx: &mut ViewContext<Self>) {
|
||||
if self.is_dismissed() {
|
||||
self.show(cx);
|
||||
self.deploy(action, cx);
|
||||
} else {
|
||||
self.dismiss(&Dismiss, cx);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum ComponentStory {
|
||||
ListHeader,
|
||||
ListItem,
|
||||
Scroll,
|
||||
Tab,
|
||||
Text,
|
||||
ZIndex,
|
||||
Picker,
|
||||
@@ -53,6 +54,7 @@ impl ComponentStory {
|
||||
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
|
||||
Self::Scroll => ScrollStory::view(cx).into(),
|
||||
Self::Text => TextStory::view(cx).into(),
|
||||
Self::Tab => cx.build_view(|_| ui::TabStory).into(),
|
||||
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
|
||||
Self::Picker => PickerStory::new(cx).into(),
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ use std::{
|
||||
use thiserror::Error;
|
||||
|
||||
use gpui::{
|
||||
px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
|
||||
actions, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
|
||||
ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
Point, ScrollWheelEvent, Size, Task, TouchPhase,
|
||||
};
|
||||
@@ -58,6 +58,16 @@ use gpui::{
|
||||
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
actions!(
|
||||
Clear,
|
||||
Copy,
|
||||
Paste,
|
||||
ShowCharacterPalette,
|
||||
SearchTest,
|
||||
SendText,
|
||||
SendKeystroke,
|
||||
);
|
||||
|
||||
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
||||
///Scroll multiplier that is set to 3 by default. This will be removed when I
|
||||
///Implement scroll bars.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
|
||||
use gpui::{
|
||||
black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext,
|
||||
AvailableSpace, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle,
|
||||
FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement,
|
||||
LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
|
||||
FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
|
||||
IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
|
||||
TextRun, TextStyle, TextSystem, UnderlineStyle, View, WhiteSpace, WindowContext,
|
||||
};
|
||||
@@ -643,13 +643,11 @@ impl TerminalElement {
|
||||
let connection = connection.clone();
|
||||
let focus = focus.clone();
|
||||
move |e, cx| {
|
||||
if e.pressed_button.is_some() {
|
||||
if focus.is_focused(cx) {
|
||||
connection.update(cx, |terminal, cx| {
|
||||
terminal.mouse_drag(e, origin, bounds);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
|
||||
connection.update(cx, |terminal, cx| {
|
||||
terminal.mouse_drag(e, origin, bounds);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -806,7 +804,28 @@ impl Element for TerminalElement {
|
||||
.map(|cursor| cursor.bounding_rect(origin)),
|
||||
};
|
||||
|
||||
let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx);
|
||||
let terminal_focus_handle = self.focus.clone();
|
||||
let terminal_handle = self.terminal.clone();
|
||||
let mut this: TerminalElement = self
|
||||
.register_mouse_listeners(origin, layout.mode, bounds, cx)
|
||||
.drag_over::<ExternalPaths>(|style| {
|
||||
// todo!() why does not it work? z-index of elements?
|
||||
style.bg(cx.theme().colors().ghost_element_hover)
|
||||
})
|
||||
.on_drop::<ExternalPaths>(move |external_paths, cx| {
|
||||
cx.focus(&terminal_focus_handle);
|
||||
let mut new_text = external_paths
|
||||
.read(cx)
|
||||
.paths()
|
||||
.iter()
|
||||
.map(|path| format!(" {path:?}"))
|
||||
.join("");
|
||||
new_text.push(' ');
|
||||
terminal_handle.update(cx, |terminal, _| {
|
||||
// todo!() long paths are not displayed properly albeit the text is there
|
||||
terminal.paste(&new_text);
|
||||
});
|
||||
});
|
||||
|
||||
let interactivity = mem::take(&mut this.interactivity);
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ pub mod terminal_panel;
|
||||
// use crate::terminal_element::TerminalElement;
|
||||
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||
use gpui::{
|
||||
actions, div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle,
|
||||
Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model,
|
||||
MouseButton, MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView,
|
||||
div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, Focusable,
|
||||
FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton,
|
||||
MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView,
|
||||
};
|
||||
use language::Bias;
|
||||
use persistence::TERMINAL_DB;
|
||||
@@ -22,7 +22,7 @@ use terminal::{
|
||||
term::{search::RegexSearch, TermMode},
|
||||
},
|
||||
terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
|
||||
Event, MaybeNavigationTarget, Terminal,
|
||||
Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
|
||||
};
|
||||
use terminal_element::TerminalElement;
|
||||
use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
|
||||
@@ -60,8 +60,6 @@ pub struct SendText(String);
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)]
|
||||
pub struct SendKeystroke(String);
|
||||
|
||||
actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
terminal_panel::init(cx);
|
||||
terminal::init(cx);
|
||||
|
||||
@@ -187,6 +187,10 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
||||
Self::set_theme(self.original_theme.clone(), cx);
|
||||
self.selection_completed = true;
|
||||
}
|
||||
|
||||
self.view
|
||||
.update(cx, |_, cx| cx.emit(DismissEvent))
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
|
||||
@@ -13,6 +13,7 @@ mod popover;
|
||||
mod popover_menu;
|
||||
mod right_click_menu;
|
||||
mod stack;
|
||||
mod tab;
|
||||
mod tooltip;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
@@ -33,6 +34,7 @@ pub use popover::*;
|
||||
pub use popover_menu::*;
|
||||
pub use right_click_menu::*;
|
||||
pub use stack::*;
|
||||
pub use tab::*;
|
||||
pub use tooltip::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
|
||||
@@ -98,11 +98,11 @@ impl RenderOnce for Key {
|
||||
|
||||
div()
|
||||
.py_0()
|
||||
.map(|el| {
|
||||
.map(|this| {
|
||||
if single_char {
|
||||
el.w(rems(14. / 16.)).flex().flex_none().justify_center()
|
||||
this.w(rems(14. / 16.)).flex().flex_none().justify_center()
|
||||
} else {
|
||||
el.px_0p5()
|
||||
this.px_0p5()
|
||||
}
|
||||
})
|
||||
.h(rems(14. / 16.))
|
||||
|
||||
@@ -10,6 +10,7 @@ mod label;
|
||||
mod list;
|
||||
mod list_header;
|
||||
mod list_item;
|
||||
mod tab;
|
||||
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
@@ -23,3 +24,4 @@ pub use label::*;
|
||||
pub use list::*;
|
||||
pub use list_header::*;
|
||||
pub use list_item::*;
|
||||
pub use tab::*;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use gpui::{Div, Render};
|
||||
use story::Story;
|
||||
|
||||
use crate::{prelude::*, TabPosition};
|
||||
use crate::{Indicator, Tab};
|
||||
|
||||
pub struct TabStory;
|
||||
|
||||
impl Render for TabStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Tab>())
|
||||
.child(Story::label("Default"))
|
||||
.child(h_stack().child(Tab::new("tab_1").child("Tab 1")))
|
||||
.child(Story::label("With indicator"))
|
||||
.child(
|
||||
h_stack().child(
|
||||
Tab::new("tab_1")
|
||||
.start_slot(Indicator::dot().color(Color::Warning))
|
||||
.child("Tab 1"),
|
||||
),
|
||||
)
|
||||
.child(Story::label("With close button"))
|
||||
.child(
|
||||
h_stack().child(
|
||||
Tab::new("tab_1")
|
||||
.end_slot(
|
||||
IconButton::new("close_button", Icon::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.icon_size(IconSize::XSmall),
|
||||
)
|
||||
.child("Tab 1"),
|
||||
),
|
||||
)
|
||||
.child(Story::label("List of tabs"))
|
||||
.child(
|
||||
h_stack()
|
||||
.child(Tab::new("tab_1").child("Tab 1"))
|
||||
.child(Tab::new("tab_2").child("Tab 2")),
|
||||
)
|
||||
.child(Story::label("List of tabs with first tab selected"))
|
||||
.child(
|
||||
h_stack()
|
||||
.child(
|
||||
Tab::new("tab_1")
|
||||
.selected(true)
|
||||
.position(TabPosition::First)
|
||||
.child("Tab 1"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_2")
|
||||
.position(TabPosition::Middle(Ordering::Greater))
|
||||
.child("Tab 2"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_3")
|
||||
.position(TabPosition::Middle(Ordering::Greater))
|
||||
.child("Tab 3"),
|
||||
)
|
||||
.child(Tab::new("tab_4").position(TabPosition::Last).child("Tab 4")),
|
||||
)
|
||||
.child(Story::label("List of tabs with last tab selected"))
|
||||
.child(
|
||||
h_stack()
|
||||
.child(
|
||||
Tab::new("tab_1")
|
||||
.position(TabPosition::First)
|
||||
.child("Tab 1"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_2")
|
||||
.position(TabPosition::Middle(Ordering::Less))
|
||||
.child("Tab 2"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_3")
|
||||
.position(TabPosition::Middle(Ordering::Less))
|
||||
.child("Tab 3"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_4")
|
||||
.position(TabPosition::Last)
|
||||
.selected(true)
|
||||
.child("Tab 4"),
|
||||
),
|
||||
)
|
||||
.child(Story::label("List of tabs with second tab selected"))
|
||||
.child(
|
||||
h_stack()
|
||||
.child(
|
||||
Tab::new("tab_1")
|
||||
.position(TabPosition::First)
|
||||
.child("Tab 1"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_2")
|
||||
.position(TabPosition::Middle(Ordering::Equal))
|
||||
.selected(true)
|
||||
.child("Tab 2"),
|
||||
)
|
||||
.child(
|
||||
Tab::new("tab_3")
|
||||
.position(TabPosition::Middle(Ordering::Greater))
|
||||
.child("Tab 3"),
|
||||
)
|
||||
.child(Tab::new("tab_4").position(TabPosition::Last).child("Tab 4")),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{AnyElement, AnyView, ClickEvent, IntoElement, MouseButton};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// The position of a [`Tab`] within a list of tabs.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum TabPosition {
|
||||
/// The tab is first in the list.
|
||||
First,
|
||||
|
||||
/// The tab is in the middle of the list (i.e., it is not the first or last tab).
|
||||
///
|
||||
/// The [`Ordering`] is where this tab is positioned with respect to the selected tab.
|
||||
Middle(Ordering),
|
||||
|
||||
/// The tab is last in the list.
|
||||
Last,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum TabCloseSide {
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Tab {
|
||||
id: ElementId,
|
||||
selected: bool,
|
||||
position: TabPosition,
|
||||
close_side: TabCloseSide,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
|
||||
start_slot: Option<AnyElement>,
|
||||
end_slot: Option<AnyElement>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
selected: false,
|
||||
position: TabPosition::First,
|
||||
close_side: TabCloseSide::End,
|
||||
on_click: None,
|
||||
tooltip: None,
|
||||
start_slot: None,
|
||||
end_slot: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(mut self, position: TabPosition) -> Self {
|
||||
self.position = position;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn close_side(mut self, close_side: TabCloseSide) -> Self {
|
||||
self.close_side = close_side;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.on_click = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.tooltip = Some(Box::new(tooltip));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn start_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
|
||||
self.start_slot = element.into().map(IntoElement::into_any_element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn end_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
|
||||
self.end_slot = element.into().map(IntoElement::into_any_element);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Selectable for Tab {
|
||||
fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Tab {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Tab {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
const HEIGHT_IN_REMS: f32 = 30. / 16.;
|
||||
|
||||
let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected {
|
||||
false => (
|
||||
cx.theme().colors().text_muted,
|
||||
cx.theme().colors().tab_inactive_background,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
true => (
|
||||
cx.theme().colors().text,
|
||||
cx.theme().colors().tab_active_background,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
};
|
||||
|
||||
div()
|
||||
.h(rems(HEIGHT_IN_REMS))
|
||||
.bg(tab_bg)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.map(|this| match self.position {
|
||||
TabPosition::First => {
|
||||
if self.selected {
|
||||
this.pl_px().border_r().pb_px()
|
||||
} else {
|
||||
this.pl_px().pr_px().border_b()
|
||||
}
|
||||
}
|
||||
TabPosition::Last => {
|
||||
if self.selected {
|
||||
this.border_l().border_r().pb_px()
|
||||
} else {
|
||||
this.pr_px().pl_px().border_b()
|
||||
}
|
||||
}
|
||||
TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(),
|
||||
TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(),
|
||||
TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(),
|
||||
})
|
||||
.child(
|
||||
h_stack()
|
||||
.group("")
|
||||
.id(self.id)
|
||||
.relative()
|
||||
.h_full()
|
||||
.px_5()
|
||||
.gap_1()
|
||||
.text_color(text_color)
|
||||
// .hover(|style| style.bg(tab_hover_bg))
|
||||
// .active(|style| style.bg(tab_active_bg))
|
||||
.when_some(self.on_click, |tab, on_click| {
|
||||
tab.cursor_pointer().on_click(move |event, cx| {
|
||||
// HACK: GPUI currently fires `on_click` with any mouse button,
|
||||
// but we only care about the left button.
|
||||
if event.down.button == MouseButton::Left {
|
||||
(on_click)(event, cx)
|
||||
}
|
||||
})
|
||||
})
|
||||
.when_some(self.tooltip, |tab, tooltip| {
|
||||
tab.tooltip(move |cx| tooltip(cx))
|
||||
})
|
||||
.child(
|
||||
h_stack()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.justify_center()
|
||||
.absolute()
|
||||
.map(|this| match self.close_side {
|
||||
TabCloseSide::Start => this.right_1(),
|
||||
TabCloseSide::End => this.left_1(),
|
||||
})
|
||||
.children(self.start_slot),
|
||||
)
|
||||
.child(
|
||||
h_stack()
|
||||
.invisible()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.justify_center()
|
||||
.absolute()
|
||||
.map(|this| match self.close_side {
|
||||
TabCloseSide::Start => this.left_1(),
|
||||
TabCloseSide::End => this.right_1(),
|
||||
})
|
||||
.group_hover("", |style| style.visible())
|
||||
.children(self.end_slot),
|
||||
)
|
||||
.children(self.children),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -133,13 +133,13 @@ pub struct Dock {
|
||||
panel_entries: Vec<PanelEntry>,
|
||||
is_open: bool,
|
||||
active_panel_index: usize,
|
||||
focus_handle: FocusHandle,
|
||||
focus_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl FocusableView for Dock {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.panel_entries[self.active_panel_index]
|
||||
.panel
|
||||
.focus_handle(cx)
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,12 +190,20 @@ pub struct PanelButtons {
|
||||
}
|
||||
|
||||
impl Dock {
|
||||
pub fn new(position: DockPosition) -> Self {
|
||||
pub fn new(position: DockPosition, cx: &mut ViewContext<'_, Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
|
||||
if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
|
||||
active_entry.panel.focus_handle(cx).focus(cx)
|
||||
}
|
||||
});
|
||||
Self {
|
||||
position,
|
||||
panel_entries: Default::default(),
|
||||
active_panel_index: 0,
|
||||
is_open: false,
|
||||
focus_handle,
|
||||
focus_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +215,7 @@ impl Dock {
|
||||
self.is_open
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// pub fn has_focus(&self, cx: &WindowContext) -> bool {
|
||||
// self.visible_panel()
|
||||
// .map_or(false, |panel| panel.has_focus(cx))
|
||||
|
||||
@@ -28,10 +28,10 @@ use std::{
|
||||
|
||||
use ui::{
|
||||
h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize,
|
||||
Indicator, Label, Tooltip,
|
||||
Indicator, Label, Tab, TabPosition, Tooltip,
|
||||
};
|
||||
use ui::{v_stack, ContextMenu};
|
||||
use util::truncate_and_remove_front;
|
||||
use util::{maybe, truncate_and_remove_front};
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -1438,42 +1438,49 @@ impl Pane {
|
||||
|
||||
let is_active = ix == self.active_item_index;
|
||||
|
||||
let indicator = {
|
||||
let indicator = maybe!({
|
||||
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
(true, _) => Color::Warning,
|
||||
(_, true) => Color::Accent,
|
||||
(false, false) => return None,
|
||||
};
|
||||
|
||||
h_stack()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.justify_center()
|
||||
.absolute()
|
||||
.map(|this| match close_side {
|
||||
ClosePosition::Left => this.right_1(),
|
||||
ClosePosition::Right => this.left_1(),
|
||||
})
|
||||
.when_some(indicator_color, |this, indicator_color| {
|
||||
this.child(Indicator::dot().color(indicator_color))
|
||||
})
|
||||
};
|
||||
Some(Indicator::dot().color(indicator_color))
|
||||
});
|
||||
|
||||
let close_button = {
|
||||
let id = item.item_id();
|
||||
let id = item.item_id();
|
||||
|
||||
h_stack()
|
||||
.invisible()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.justify_center()
|
||||
.absolute()
|
||||
.map(|this| match close_side {
|
||||
ClosePosition::Left => this.left_1(),
|
||||
ClosePosition::Right => this.right_1(),
|
||||
let is_first_item = ix == 0;
|
||||
let is_last_item = ix == self.items.len() - 1;
|
||||
let position_relative_to_active_item = ix.cmp(&self.active_item_index);
|
||||
|
||||
let tab =
|
||||
Tab::new(ix)
|
||||
.position(if is_first_item {
|
||||
TabPosition::First
|
||||
} else if is_last_item {
|
||||
TabPosition::Last
|
||||
} else {
|
||||
TabPosition::Middle(position_relative_to_active_item)
|
||||
})
|
||||
.group_hover("", |style| style.visible())
|
||||
.child(
|
||||
.close_side(match close_side {
|
||||
ClosePosition::Left => ui::TabCloseSide::Start,
|
||||
ClosePosition::Right => ui::TabCloseSide::End,
|
||||
})
|
||||
.selected(ix == self.active_item_index())
|
||||
.on_click(cx.listener(move |pane: &mut Self, event, cx| {
|
||||
pane.activate_item(ix, true, true, cx)
|
||||
}))
|
||||
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
||||
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
||||
// eprintln!("{:?}", state.read(cx));
|
||||
// })
|
||||
.when_some(item.tab_tooltip_text(cx), |tab, text| {
|
||||
tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
|
||||
})
|
||||
.start_slot::<Indicator>(indicator)
|
||||
.end_slot(
|
||||
// TODO: Fix button size
|
||||
IconButton::new("close tab", Icon::Close)
|
||||
.icon_color(Color::Muted)
|
||||
@@ -1484,67 +1491,7 @@ impl Pane {
|
||||
.detach_and_log_err(cx);
|
||||
})),
|
||||
)
|
||||
};
|
||||
|
||||
let tab = div()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(tab_bg)
|
||||
// 30px @ 16px/rem
|
||||
.h(rems(1.875))
|
||||
.map(|this| {
|
||||
let is_first_item = ix == 0;
|
||||
let is_last_item = ix == self.items.len() - 1;
|
||||
match ix.cmp(&self.active_item_index) {
|
||||
cmp::Ordering::Less => {
|
||||
if is_first_item {
|
||||
this.pl_px().pr_px().border_b()
|
||||
} else {
|
||||
this.border_l().pr_px().border_b()
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
if is_last_item {
|
||||
this.pr_px().pl_px().border_b()
|
||||
} else {
|
||||
this.border_r().pl_px().border_b()
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Equal => {
|
||||
if is_first_item {
|
||||
this.pl_px().border_r().pb_px()
|
||||
} else {
|
||||
this.border_l().border_r().pb_px()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.child(
|
||||
h_stack()
|
||||
.group("")
|
||||
.id(ix)
|
||||
.relative()
|
||||
.h_full()
|
||||
.cursor_pointer()
|
||||
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
||||
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)),
|
||||
)
|
||||
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
||||
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
||||
// eprintln!("{:?}", state.read(cx));
|
||||
// })
|
||||
.px_5()
|
||||
// .hover(|h| h.bg(tab_hover_bg))
|
||||
// .active(|a| a.bg(tab_active_bg))
|
||||
.gap_1()
|
||||
.text_color(text_color)
|
||||
.child(indicator)
|
||||
.child(close_button)
|
||||
.child(label),
|
||||
);
|
||||
.child(label);
|
||||
|
||||
right_click_menu(ix).trigger(tab).menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, cx| {
|
||||
|
||||
@@ -566,9 +566,9 @@ impl Workspace {
|
||||
|
||||
cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
|
||||
|
||||
let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
|
||||
let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
|
||||
let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
|
||||
let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx));
|
||||
let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx));
|
||||
let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx));
|
||||
let left_dock_buttons =
|
||||
cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
|
||||
let bottom_dock_buttons =
|
||||
@@ -4188,14 +4188,14 @@ pub fn open_paths(
|
||||
});
|
||||
cx.spawn(move |mut cx| async move {
|
||||
if let Some(existing) = existing {
|
||||
// // Ok((
|
||||
// existing.clone(),
|
||||
// cx.update_window_root(&existing, |workspace, cx| {
|
||||
// workspace.open_paths(abs_paths, true, cx)
|
||||
// })?
|
||||
// .await,
|
||||
// ))
|
||||
todo!()
|
||||
Ok((
|
||||
existing.clone(),
|
||||
existing
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_paths(abs_paths, true, cx)
|
||||
})?
|
||||
.await,
|
||||
))
|
||||
} else {
|
||||
cx.update(move |cx| {
|
||||
Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
|
||||
|
||||
Reference in New Issue
Block a user