Merge branch 'main' into feedback-modal-ui

This commit is contained in:
Joseph T. Lyons
2023-12-07 12:58:53 -05:00
26 changed files with 702 additions and 415 deletions
+21 -28
View File
@@ -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,
+12 -12
View File
@@ -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(
+59 -58
View 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
View File
@@ -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 {
-18
View File
@@ -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,
+6
View File
@@ -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
View File
@@ -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
}
+1
View File
@@ -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)
});
}
},
))
+1
View File
@@ -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();
+22 -11
View File
@@ -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);
}
+2
View File
@@ -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(),
}
+11 -1
View File
@@ -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.
+30 -11
View File
@@ -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);
+4 -6
View File
@@ -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 {
+2
View File
@@ -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")]
+3 -3
View File
@@ -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.))
+2
View File
@@ -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::*;
+114
View File
@@ -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")),
)
}
}
+198
View File
@@ -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),
)
}
}
+14 -5
View File
@@ -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))
+40 -93
View File
@@ -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| {
+11 -11
View File
@@ -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)