Detailed changes
@@ -32,6 +32,16 @@
// 4. Save when idle for a certain amount of time:
// "autosave": { "after_delay": {"milliseconds": 500} },
"autosave": "off",
+ // Where to place the dock by default. This setting can take three
+ // values:
+ //
+ // 1. Position the dock attached to the bottom of the workspace
+ // "default_dock_anchor": "bottom"
+ // 2. Position the dock to the right of the workspace like a side panel
+ // "default_dock_anchor": "right"
+ // 3. Position the dock full screen over the entire workspace"
+ // "default_dock_anchor": "expanded"
+ "default_dock_anchor": "right",
// How to auto-format modified buffers when saving them. This
// setting can take three values:
//
@@ -28,7 +28,7 @@ use gpui::{
text_layout::{self, Line, RunStyle, TextLayoutCache},
AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext,
LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent,
- MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext,
+ MouseRegion, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext,
WeakViewHandle,
};
use json::json;
@@ -41,6 +41,7 @@ use std::{
fmt::Write,
iter,
ops::Range,
+ sync::Arc,
};
const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
@@ -76,9 +77,10 @@ impl SelectionLayout {
}
}
+#[derive(Clone)]
pub struct EditorElement {
view: WeakViewHandle<Editor>,
- style: EditorStyle,
+ style: Arc<EditorStyle>,
cursor_shape: CursorShape,
}
@@ -90,7 +92,7 @@ impl EditorElement {
) -> Self {
Self {
view,
- style,
+ style: Arc::new(style),
cursor_shape,
}
}
@@ -110,8 +112,98 @@ impl EditorElement {
self.update_view(cx, |view, cx| view.snapshot(cx))
}
+ fn attach_mouse_handlers(
+ view: &WeakViewHandle<Editor>,
+ position_map: &Arc<PositionMap>,
+ visible_bounds: RectF,
+ text_bounds: RectF,
+ gutter_bounds: RectF,
+ bounds: RectF,
+ cx: &mut PaintContext,
+ ) {
+ enum EditorElementMouseHandlers {}
+ cx.scene.push_mouse_region(
+ MouseRegion::new::<EditorElementMouseHandlers>(view.id(), view.id(), visible_bounds)
+ .on_down(MouseButton::Left, {
+ let position_map = position_map.clone();
+ move |e, cx| {
+ if !Self::mouse_down(
+ e.platform_event,
+ position_map.as_ref(),
+ text_bounds,
+ gutter_bounds,
+ cx,
+ ) {
+ cx.propogate_event();
+ }
+ }
+ })
+ .on_down(MouseButton::Right, {
+ let position_map = position_map.clone();
+ move |e, cx| {
+ if !Self::mouse_right_down(
+ e.position,
+ position_map.as_ref(),
+ text_bounds,
+ cx,
+ ) {
+ cx.propogate_event();
+ }
+ }
+ })
+ .on_up(MouseButton::Left, {
+ let view = view.clone();
+ let position_map = position_map.clone();
+ move |e, cx| {
+ if !Self::mouse_up(
+ view.clone(),
+ e.position,
+ e.cmd,
+ e.shift,
+ position_map.as_ref(),
+ text_bounds,
+ cx,
+ ) {
+ cx.propogate_event()
+ }
+ }
+ })
+ .on_drag(MouseButton::Left, {
+ let view = view.clone();
+ let position_map = position_map.clone();
+ move |e, cx| {
+ if !Self::mouse_dragged(
+ view.clone(),
+ e.platform_event,
+ position_map.as_ref(),
+ text_bounds,
+ cx,
+ ) {
+ cx.propogate_event()
+ }
+ }
+ })
+ .on_move({
+ let position_map = position_map.clone();
+ move |e, cx| {
+ if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
+ cx.propogate_event()
+ }
+ }
+ })
+ .on_scroll({
+ let position_map = position_map.clone();
+ move |e, cx| {
+ if !Self::scroll(e.position, e.delta, e.precise, &position_map, bounds, cx)
+ {
+ cx.propogate_event()
+ }
+ }
+ }),
+ );
+ }
+
fn mouse_down(
- &self,
MouseButtonEvent {
position,
ctrl,
@@ -121,18 +213,18 @@ impl EditorElement {
mut click_count,
..
}: MouseButtonEvent,
- layout: &mut LayoutState,
- paint: &mut PaintState,
+ position_map: &PositionMap,
+ text_bounds: RectF,
+ gutter_bounds: RectF,
cx: &mut EventContext,
) -> bool {
- if paint.gutter_bounds.contains_point(position) {
+ if gutter_bounds.contains_point(position) {
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
- } else if !paint.text_bounds.contains_point(position) {
+ } else if !text_bounds.contains_point(position) {
return false;
}
- let snapshot = self.snapshot(cx.app);
- let (position, target_position) = paint.point_for_position(&snapshot, layout, position);
+ let (position, target_position) = position_map.point_for_position(text_bounds, position);
if shift && alt {
cx.dispatch_action(Select(SelectPhase::BeginColumnar {
@@ -156,33 +248,31 @@ impl EditorElement {
}
fn mouse_right_down(
- &self,
position: Vector2F,
- layout: &mut LayoutState,
- paint: &mut PaintState,
+ position_map: &PositionMap,
+ text_bounds: RectF,
cx: &mut EventContext,
) -> bool {
- if !paint.text_bounds.contains_point(position) {
+ if !text_bounds.contains_point(position) {
return false;
}
- let snapshot = self.snapshot(cx.app);
- let (point, _) = paint.point_for_position(&snapshot, layout, position);
+ let (point, _) = position_map.point_for_position(text_bounds, position);
cx.dispatch_action(DeployMouseContextMenu { position, point });
true
}
fn mouse_up(
- &self,
+ view: WeakViewHandle<Editor>,
position: Vector2F,
cmd: bool,
shift: bool,
- layout: &mut LayoutState,
- paint: &mut PaintState,
+ position_map: &PositionMap,
+ text_bounds: RectF,
cx: &mut EventContext,
) -> bool {
- let view = self.view(cx.app.as_ref());
+ let view = view.upgrade(cx.app).unwrap().read(cx.app);
let end_selection = view.has_pending_selection();
let pending_nonempty_selections = view.has_pending_nonempty_selection();
@@ -190,9 +280,8 @@ impl EditorElement {
cx.dispatch_action(Select(SelectPhase::End));
}
- if !pending_nonempty_selections && cmd && paint.text_bounds.contains_point(position) {
- let (point, target_point) =
- paint.point_for_position(&self.snapshot(cx), layout, position);
+ if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
+ let (point, target_point) = position_map.point_for_position(text_bounds, position);
if point == target_point {
if shift {
@@ -209,22 +298,21 @@ impl EditorElement {
}
fn mouse_dragged(
- &self,
+ view: WeakViewHandle<Editor>,
MouseMovedEvent {
cmd,
shift,
position,
..
}: MouseMovedEvent,
- layout: &mut LayoutState,
- paint: &mut PaintState,
+ position_map: &PositionMap,
+ text_bounds: RectF,
cx: &mut EventContext,
) -> bool {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
// Don't trigger hover popover if mouse is hovering over context menu
- let point = if paint.text_bounds.contains_point(position) {
- let (point, target_point) =
- paint.point_for_position(&self.snapshot(cx), layout, position);
+ let point = if text_bounds.contains_point(position) {
+ let (point, target_point) = position_map.point_for_position(text_bounds, position);
if point == target_point {
Some(point)
} else {
@@ -240,14 +328,13 @@ impl EditorElement {
shift_held: shift,
});
- let view = self.view(cx.app);
+ let view = view.upgrade(cx.app).unwrap().read(cx.app);
if view.has_pending_selection() {
- let rect = paint.text_bounds;
let mut scroll_delta = Vector2F::zero();
- let vertical_margin = layout.line_height.min(rect.height() / 3.0);
- let top = rect.origin_y() + vertical_margin;
- let bottom = rect.lower_left().y() - vertical_margin;
+ let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
+ let top = text_bounds.origin_y() + vertical_margin;
+ let bottom = text_bounds.lower_left().y() - vertical_margin;
if position.y() < top {
scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
}
@@ -255,9 +342,9 @@ impl EditorElement {
scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
}
- let horizontal_margin = layout.line_height.min(rect.width() / 3.0);
- let left = rect.origin_x() + horizontal_margin;
- let right = rect.upper_right().x() - horizontal_margin;
+ let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
+ let left = text_bounds.origin_x() + horizontal_margin;
+ let right = text_bounds.upper_right().x() - horizontal_margin;
if position.x() < left {
scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
left - position.x(),
@@ -269,14 +356,14 @@ impl EditorElement {
))
}
- let snapshot = self.snapshot(cx.app);
- let (position, target_position) = paint.point_for_position(&snapshot, layout, position);
+ let (position, target_position) =
+ position_map.point_for_position(text_bounds, position);
cx.dispatch_action(Select(SelectPhase::Update {
position,
goal_column: target_position.column(),
- scroll_position: (snapshot.scroll_position() + scroll_delta)
- .clamp(Vector2F::zero(), layout.scroll_max),
+ scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
+ .clamp(Vector2F::zero(), position_map.scroll_max),
}));
cx.dispatch_action(HoverAt { point });
@@ -288,22 +375,20 @@ impl EditorElement {
}
fn mouse_moved(
- &self,
MouseMovedEvent {
cmd,
shift,
position,
..
}: MouseMovedEvent,
- layout: &LayoutState,
- paint: &PaintState,
+ position_map: &PositionMap,
+ text_bounds: RectF,
cx: &mut EventContext,
) -> bool {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
// Don't trigger hover popover if mouse is hovering over context menu
- let point = if paint.text_bounds.contains_point(position) {
- let (point, target_point) =
- paint.point_for_position(&self.snapshot(cx), layout, position);
+ let point = if text_bounds.contains_point(position) {
+ let (point, target_point) = position_map.point_for_position(text_bounds, position);
if point == target_point {
Some(point)
} else {
@@ -319,23 +404,6 @@ impl EditorElement {
shift_held: shift,
});
- if paint
- .context_menu_bounds
- .map_or(false, |context_menu_bounds| {
- context_menu_bounds.contains_point(position)
- })
- {
- return false;
- }
-
- if paint
- .hover_popover_bounds
- .iter()
- .any(|hover_bounds| hover_bounds.contains_point(position))
- {
- return false;
- }
-
cx.dispatch_action(HoverAt { point });
true
}
@@ -349,28 +417,27 @@ impl EditorElement {
}
fn scroll(
- &self,
position: Vector2F,
mut delta: Vector2F,
precise: bool,
- layout: &mut LayoutState,
- paint: &mut PaintState,
+ position_map: &PositionMap,
+ bounds: RectF,
cx: &mut EventContext,
) -> bool {
- if !paint.bounds.contains_point(position) {
+ if !bounds.contains_point(position) {
return false;
}
- let snapshot = self.snapshot(cx.app);
- let max_glyph_width = layout.em_width;
+ let max_glyph_width = position_map.em_width;
if !precise {
- delta *= vec2f(max_glyph_width, layout.line_height);
+ delta *= vec2f(max_glyph_width, position_map.line_height);
}
- let scroll_position = snapshot.scroll_position();
+ let scroll_position = position_map.snapshot.scroll_position();
let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
- let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height;
- let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max);
+ let y =
+ (scroll_position.y() * position_map.line_height - delta.y()) / position_map.line_height;
+ let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), position_map.scroll_max);
cx.dispatch_action(Scroll(scroll_position));
@@ -385,7 +452,8 @@ impl EditorElement {
cx: &mut PaintContext,
) {
let bounds = gutter_bounds.union_rect(text_bounds);
- let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
+ let scroll_top =
+ layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
let editor = self.view(cx.app);
cx.scene.push_quad(Quad {
bounds: gutter_bounds,
@@ -414,11 +482,12 @@ impl EditorElement {
if !contains_non_empty_selection {
let origin = vec2f(
bounds.origin_x(),
- bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top,
+ bounds.origin_y() + (layout.position_map.line_height * *start_row as f32)
+ - scroll_top,
);
let size = vec2f(
bounds.width(),
- layout.line_height * (end_row - start_row + 1) as f32,
+ layout.position_map.line_height * (end_row - start_row + 1) as f32,
);
cx.scene.push_quad(Quad {
bounds: RectF::new(origin, size),
@@ -432,12 +501,13 @@ impl EditorElement {
if let Some(highlighted_rows) = &layout.highlighted_rows {
let origin = vec2f(
bounds.origin_x(),
- bounds.origin_y() + (layout.line_height * highlighted_rows.start as f32)
+ bounds.origin_y()
+ + (layout.position_map.line_height * highlighted_rows.start as f32)
- scroll_top,
);
let size = vec2f(
bounds.width(),
- layout.line_height * highlighted_rows.len() as f32,
+ layout.position_map.line_height * highlighted_rows.len() as f32,
);
cx.scene.push_quad(Quad {
bounds: RectF::new(origin, size),
@@ -456,23 +526,30 @@ impl EditorElement {
layout: &mut LayoutState,
cx: &mut PaintContext,
) {
- let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
+ let scroll_top =
+ layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
if let Some(line) = line {
let line_origin = bounds.origin()
+ vec2f(
bounds.width() - line.width() - layout.gutter_padding,
- ix as f32 * layout.line_height - (scroll_top % layout.line_height),
+ ix as f32 * layout.position_map.line_height
+ - (scroll_top % layout.position_map.line_height),
);
- line.paint(line_origin, visible_bounds, layout.line_height, cx);
+ line.paint(
+ line_origin,
+ visible_bounds,
+ layout.position_map.line_height,
+ cx,
+ );
}
}
if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
let mut x = bounds.width() - layout.gutter_padding;
- let mut y = *row as f32 * layout.line_height - scroll_top;
+ let mut y = *row as f32 * layout.position_map.line_height - scroll_top;
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
- y += (layout.line_height - indicator.size().y()) / 2.;
+ y += (layout.position_map.line_height - indicator.size().y()) / 2.;
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
}
}
@@ -488,11 +565,12 @@ impl EditorElement {
let view = self.view(cx.app);
let style = &self.style;
let local_replica_id = view.replica_id(cx);
- let scroll_position = layout.snapshot.scroll_position();
+ let scroll_position = layout.position_map.snapshot.scroll_position();
let start_row = scroll_position.y() as u32;
- let scroll_top = scroll_position.y() * layout.line_height;
- let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
- let max_glyph_width = layout.em_width;
+ let scroll_top = scroll_position.y() * layout.position_map.line_height;
+ let end_row =
+ ((scroll_top + bounds.height()) / layout.position_map.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
+ let max_glyph_width = layout.position_map.em_width;
let scroll_left = scroll_position.x() * max_glyph_width;
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
@@ -514,7 +592,7 @@ impl EditorElement {
end_row,
*color,
0.,
- 0.15 * layout.line_height,
+ 0.15 * layout.position_map.line_height,
layout,
content_origin,
scroll_top,
@@ -527,7 +605,7 @@ impl EditorElement {
let mut cursors = SmallVec::<[Cursor; 32]>::new();
for (replica_id, selections) in &layout.selections {
let selection_style = style.replica_selection_style(*replica_id);
- let corner_radius = 0.15 * layout.line_height;
+ let corner_radius = 0.15 * layout.position_map.line_height;
for selection in selections {
self.paint_highlighted_range(
@@ -548,50 +626,52 @@ impl EditorElement {
if view.show_local_cursors() || *replica_id != local_replica_id {
let cursor_position = selection.head;
if (start_row..end_row).contains(&cursor_position.row()) {
- let cursor_row_layout =
- &layout.line_layouts[(cursor_position.row() - start_row) as usize];
+ let cursor_row_layout = &layout.position_map.line_layouts
+ [(cursor_position.row() - start_row) as usize];
let cursor_column = cursor_position.column() as usize;
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
let mut block_width =
cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
if block_width == 0.0 {
- block_width = layout.em_width;
+ block_width = layout.position_map.em_width;
}
-
- let block_text =
- if let CursorShape::Block = self.cursor_shape {
- layout.snapshot.chars_at(cursor_position).next().and_then(
- |character| {
- let font_id =
- cursor_row_layout.font_for_index(cursor_column)?;
- let text = character.to_string();
-
- Some(cx.text_layout_cache.layout_str(
- &text,
- cursor_row_layout.font_size(),
- &[(
- text.len(),
- RunStyle {
- font_id,
- color: style.background,
- underline: Default::default(),
- },
- )],
- ))
- },
- )
- } else {
- None
- };
+ let block_text = if let CursorShape::Block = self.cursor_shape {
+ layout
+ .position_map
+ .snapshot
+ .chars_at(cursor_position)
+ .next()
+ .and_then(|character| {
+ let font_id =
+ cursor_row_layout.font_for_index(cursor_column)?;
+ let text = character.to_string();
+
+ Some(cx.text_layout_cache.layout_str(
+ &text,
+ cursor_row_layout.font_size(),
+ &[(
+ text.len(),
+ RunStyle {
+ font_id,
+ color: style.background,
+ underline: Default::default(),
+ },
+ )],
+ ))
+ })
+ } else {
+ None
+ };
let x = cursor_character_x - scroll_left;
- let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
+ let y = cursor_position.row() as f32 * layout.position_map.line_height
+ - scroll_top;
cursors.push(Cursor {
color: selection_style.cursor,
block_width,
origin: vec2f(x, y),
- line_height: layout.line_height,
+ line_height: layout.position_map.line_height,
shape: self.cursor_shape,
block_text,
});
@@ -602,13 +682,16 @@ impl EditorElement {
if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
// Draw glyphs
- for (ix, line) in layout.line_layouts.iter().enumerate() {
+ for (ix, line) in layout.position_map.line_layouts.iter().enumerate() {
let row = start_row + ix as u32;
line.paint(
content_origin
- + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top),
+ + vec2f(
+ -scroll_left,
+ row as f32 * layout.position_map.line_height - scroll_top,
+ ),
visible_text_bounds,
- layout.line_height,
+ layout.position_map.line_height,
cx,
);
}
@@ -622,9 +705,10 @@ impl EditorElement {
if let Some((position, context_menu)) = layout.context_menu.as_mut() {
cx.scene.push_stacking_context(None);
- let cursor_row_layout = &layout.line_layouts[(position.row() - start_row) as usize];
+ let cursor_row_layout =
+ &layout.position_map.line_layouts[(position.row() - start_row) as usize];
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
- let y = (position.row() + 1) as f32 * layout.line_height - scroll_top;
+ let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
let mut list_origin = content_origin + vec2f(x, y);
let list_width = context_menu.size().x();
let list_height = context_menu.size().y();
@@ -636,7 +720,7 @@ impl EditorElement {
}
if list_origin.y() + list_height > bounds.max_y() {
- list_origin.set_y(list_origin.y() - layout.line_height - list_height);
+ list_origin.set_y(list_origin.y() - layout.position_map.line_height - list_height);
}
context_menu.paint(
@@ -654,18 +738,19 @@ impl EditorElement {
cx.scene.push_stacking_context(None);
// This is safe because we check on layout whether the required row is available
- let hovered_row_layout = &layout.line_layouts[(position.row() - start_row) as usize];
+ let hovered_row_layout =
+ &layout.position_map.line_layouts[(position.row() - start_row) as usize];
// Minimum required size: Take the first popover, and add 1.5 times the minimum popover
// height. This is the size we will use to decide whether to render popovers above or below
// the hovered line.
let first_size = hover_popovers[0].size();
- let height_to_reserve =
- first_size.y() + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.line_height;
+ let height_to_reserve = first_size.y()
+ + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;
// Compute Hovered Point
let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
- let y = position.row() as f32 * layout.line_height - scroll_top;
+ let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
let hovered_point = content_origin + vec2f(x, y);
paint.hover_popover_bounds.clear();
@@ -697,7 +782,7 @@ impl EditorElement {
}
} else {
// There is not enough space above. Render popovers below the hovered point
- let mut current_y = hovered_point.y() + layout.line_height;
+ let mut current_y = hovered_point.y() + layout.position_map.line_height;
for hover_popover in hover_popovers {
let size = hover_popover.size();
let mut popover_origin = vec2f(hovered_point.x(), current_y);
@@ -753,14 +838,16 @@ impl EditorElement {
let highlighted_range = HighlightedRange {
color,
- line_height: layout.line_height,
+ line_height: layout.position_map.line_height,
corner_radius,
- start_y: content_origin.y() + row_range.start as f32 * layout.line_height
+ start_y: content_origin.y()
+ + row_range.start as f32 * layout.position_map.line_height
- scroll_top,
lines: row_range
.into_iter()
.map(|row| {
- let line_layout = &layout.line_layouts[(row - start_row) as usize];
+ let line_layout =
+ &layout.position_map.line_layouts[(row - start_row) as usize];
HighlightedRangeLine {
start_x: if row == range.start.row() {
content_origin.x()
@@ -793,13 +880,16 @@ impl EditorElement {
layout: &mut LayoutState,
cx: &mut PaintContext,
) {
- let scroll_position = layout.snapshot.scroll_position();
- let scroll_left = scroll_position.x() * layout.em_width;
- let scroll_top = scroll_position.y() * layout.line_height;
+ let scroll_position = layout.position_map.snapshot.scroll_position();
+ let scroll_left = scroll_position.x() * layout.position_map.em_width;
+ let scroll_top = scroll_position.y() * layout.position_map.line_height;
for block in &mut layout.blocks {
- let mut origin =
- bounds.origin() + vec2f(0., block.row as f32 * layout.line_height - scroll_top);
+ let mut origin = bounds.origin()
+ + vec2f(
+ 0.,
+ block.row as f32 * layout.position_map.line_height - scroll_top,
+ );
if !matches!(block.style, BlockStyle::Sticky) {
origin += vec2f(-scroll_left, 0.);
}
@@ -1483,22 +1573,24 @@ impl Element for EditorElement {
(
size,
LayoutState {
- size,
- scroll_max,
+ position_map: Arc::new(PositionMap {
+ size,
+ scroll_max,
+ line_layouts,
+ line_height,
+ em_width,
+ em_advance,
+ snapshot,
+ }),
gutter_size,
gutter_padding,
text_size,
gutter_margin,
- snapshot,
active_rows,
highlighted_rows,
highlighted_ranges,
- line_layouts,
line_number_layouts,
blocks,
- line_height,
- em_width,
- em_advance,
selections,
context_menu,
code_actions_indicator,
@@ -1523,13 +1615,20 @@ impl Element for EditorElement {
);
let mut paint_state = PaintState {
- bounds,
- gutter_bounds,
- text_bounds,
context_menu_bounds: None,
hover_popover_bounds: Default::default(),
};
+ Self::attach_mouse_handlers(
+ &self.view,
+ &layout.position_map,
+ visible_bounds,
+ text_bounds,
+ gutter_bounds,
+ bounds,
+ cx,
+ );
+
self.paint_background(gutter_bounds, text_bounds, layout, cx);
if layout.gutter_size.x() > 0. {
self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
@@ -1552,78 +1651,15 @@ impl Element for EditorElement {
event: &Event,
_: RectF,
_: RectF,
- layout: &mut LayoutState,
- paint: &mut PaintState,
+ _: &mut LayoutState,
+ _: &mut PaintState,
cx: &mut EventContext,
) -> bool {
- if let Some((_, context_menu)) = &mut layout.context_menu {
- if context_menu.dispatch_event(event, cx) {
- return true;
- }
- }
-
- if let Some((_, indicator)) = &mut layout.code_actions_indicator {
- if indicator.dispatch_event(event, cx) {
- return true;
- }
+ if let Event::ModifiersChanged(event) = event {
+ self.modifiers_changed(*event, cx);
}
- if let Some((_, popover_elements)) = &mut layout.hover_popovers {
- for popover_element in popover_elements.iter_mut() {
- if popover_element.dispatch_event(event, cx) {
- return true;
- }
- }
- }
-
- for block in &mut layout.blocks {
- if block.element.dispatch_event(event, cx) {
- return true;
- }
- }
-
- match event {
- &Event::MouseDown(
- event @ MouseButtonEvent {
- button: MouseButton::Left,
- ..
- },
- ) => self.mouse_down(event, layout, paint, cx),
-
- &Event::MouseDown(MouseButtonEvent {
- button: MouseButton::Right,
- position,
- ..
- }) => self.mouse_right_down(position, layout, paint, cx),
-
- &Event::MouseUp(MouseButtonEvent {
- button: MouseButton::Left,
- position,
- cmd,
- shift,
- ..
- }) => self.mouse_up(position, cmd, shift, layout, paint, cx),
-
- Event::MouseMoved(
- event @ MouseMovedEvent {
- pressed_button: Some(MouseButton::Left),
- ..
- },
- ) => self.mouse_dragged(*event, layout, paint, cx),
-
- Event::ScrollWheel(ScrollWheelEvent {
- position,
- delta,
- precise,
- ..
- }) => self.scroll(*position, *delta, *precise, layout, paint, cx),
-
- &Event::ModifiersChanged(event) => self.modifiers_changed(event, cx),
-
- &Event::MouseMoved(event) => self.mouse_moved(event, layout, paint, cx),
-
- _ => false,
- }
+ false
}
fn rect_for_text_range(
@@ -1640,26 +1676,34 @@ impl Element for EditorElement {
layout.text_size,
);
let content_origin = text_bounds.origin() + vec2f(layout.gutter_margin, 0.);
- let scroll_position = layout.snapshot.scroll_position();
+ let scroll_position = layout.position_map.snapshot.scroll_position();
let start_row = scroll_position.y() as u32;
- let scroll_top = scroll_position.y() * layout.line_height;
- let scroll_left = scroll_position.x() * layout.em_width;
+ let scroll_top = scroll_position.y() * layout.position_map.line_height;
+ let scroll_left = scroll_position.x() * layout.position_map.em_width;
- let range_start =
- OffsetUtf16(range_utf16.start).to_display_point(&layout.snapshot.display_snapshot);
+ let range_start = OffsetUtf16(range_utf16.start)
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
if range_start.row() < start_row {
return None;
}
let line = layout
+ .position_map
.line_layouts
.get((range_start.row() - start_row) as usize)?;
let range_start_x = line.x_for_index(range_start.column() as usize);
- let range_start_y = range_start.row() as f32 * layout.line_height;
+ let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
Some(RectF::new(
- content_origin + vec2f(range_start_x, range_start_y + layout.line_height)
+ content_origin
+ + vec2f(
+ range_start_x,
+ range_start_y + layout.position_map.line_height,
+ )
- vec2f(scroll_left, scroll_top),
- vec2f(layout.em_width, layout.line_height),
+ vec2f(
+ layout.position_map.em_width,
+ layout.position_map.line_height,
+ ),
))
}
@@ -1678,21 +1722,15 @@ impl Element for EditorElement {
}
pub struct LayoutState {
- size: Vector2F,
- scroll_max: Vector2F,
+ position_map: Arc<PositionMap>,
gutter_size: Vector2F,
gutter_padding: f32,
gutter_margin: f32,
text_size: Vector2F,
- snapshot: EditorSnapshot,
active_rows: BTreeMap<u32, bool>,
highlighted_rows: Option<Range<u32>>,
- line_layouts: Vec<text_layout::Line>,
line_number_layouts: Vec<Option<text_layout::Line>>,
blocks: Vec<BlockLayout>,
- line_height: f32,
- em_width: f32,
- em_advance: f32,
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
context_menu: Option<(DisplayPoint, ElementBox)>,
@@ -1700,6 +1738,52 @@ pub struct LayoutState {
hover_popovers: Option<(DisplayPoint, Vec<ElementBox>)>,
}
+pub struct PositionMap {
+ size: Vector2F,
+ line_height: f32,
+ scroll_max: Vector2F,
+ em_width: f32,
+ em_advance: f32,
+ line_layouts: Vec<text_layout::Line>,
+ snapshot: EditorSnapshot,
+}
+
+impl PositionMap {
+ /// Returns two display points:
+ /// 1. The nearest *valid* position in the editor
+ /// 2. An unclipped, potentially *invalid* position that maps directly to
+ /// the given pixel position.
+ fn point_for_position(
+ &self,
+ text_bounds: RectF,
+ position: Vector2F,
+ ) -> (DisplayPoint, DisplayPoint) {
+ let scroll_position = self.snapshot.scroll_position();
+ let position = position - text_bounds.origin();
+ let y = position.y().max(0.0).min(self.size.y());
+ let x = position.x() + (scroll_position.x() * self.em_width);
+ let row = (y / self.line_height + scroll_position.y()) as u32;
+ let (column, x_overshoot) = if let Some(line) = self
+ .line_layouts
+ .get(row as usize - scroll_position.y() as usize)
+ {
+ if let Some(ix) = line.index_for_x(x) {
+ (ix as u32, 0.0)
+ } else {
+ (line.len() as u32, 0f32.max(x - line.width()))
+ }
+ } else {
+ (0, x)
+ };
+
+ let mut target_point = DisplayPoint::new(row, column);
+ let point = self.snapshot.clip_point(target_point, Bias::Left);
+ *target_point.column_mut() += (x_overshoot / self.em_advance) as u32;
+
+ (point, target_point)
+ }
+}
+
struct BlockLayout {
row: u32,
element: ElementBox,
@@ -1738,50 +1822,10 @@ fn layout_line(
}
pub struct PaintState {
- bounds: RectF,
- gutter_bounds: RectF,
- text_bounds: RectF,
context_menu_bounds: Option<RectF>,
hover_popover_bounds: Vec<RectF>,
}
-impl PaintState {
- /// Returns two display points:
- /// 1. The nearest *valid* position in the editor
- /// 2. An unclipped, potentially *invalid* position that maps directly to
- /// the given pixel position.
- fn point_for_position(
- &self,
- snapshot: &EditorSnapshot,
- layout: &LayoutState,
- position: Vector2F,
- ) -> (DisplayPoint, DisplayPoint) {
- let scroll_position = snapshot.scroll_position();
- let position = position - self.text_bounds.origin();
- let y = position.y().max(0.0).min(layout.size.y());
- let x = position.x() + (scroll_position.x() * layout.em_width);
- let row = (y / layout.line_height + scroll_position.y()) as u32;
- let (column, x_overshoot) = if let Some(line) = layout
- .line_layouts
- .get(row as usize - scroll_position.y() as usize)
- {
- if let Some(ix) = line.index_for_x(x) {
- (ix as u32, 0.0)
- } else {
- (line.len() as u32, 0f32.max(x - line.width()))
- }
- } else {
- (0, x)
- };
-
- let mut target_point = DisplayPoint::new(row, column);
- let point = snapshot.clip_point(target_point, Bias::Left);
- *target_point.column_mut() += (x_overshoot / layout.em_advance) as u32;
-
- (point, target_point)
- }
-}
-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum CursorShape {
Bar,
@@ -4028,7 +4028,7 @@ pub struct RenderParams {
pub view_id: usize,
pub titlebar_height: f32,
pub hovered_region_ids: HashSet<MouseRegionId>,
- pub clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
+ pub clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
pub refreshing: bool,
}
@@ -4037,7 +4037,7 @@ pub struct RenderContext<'a, T: View> {
pub(crate) view_id: usize,
pub(crate) view_type: PhantomData<T>,
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
- pub(crate) clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
+ pub(crate) clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
pub app: &'a mut MutableAppContext,
pub titlebar_height: f32,
pub refreshing: bool,
@@ -1,7 +1,7 @@
use crate::{
- geometry::vector::Vector2F, presenter::MeasurementContext, CursorRegion, DebugContext, Element,
- ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseRegion,
- NavigationDirection, PaintContext, SizeConstraint,
+ geometry::vector::Vector2F, presenter::MeasurementContext, scene::HandlerSet, CursorRegion,
+ DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton,
+ MouseButtonEvent, MouseRegion, NavigationDirection, PaintContext, SizeConstraint,
};
use pathfinder_geometry::rect::RectF;
use serde_json::json;
@@ -82,11 +82,13 @@ impl Element for EventHandler {
bounds: visible_bounds,
style: Default::default(),
});
- cx.scene.push_mouse_region(MouseRegion::handle_all(
- cx.current_view_id(),
- Some(discriminant),
- visible_bounds,
- ));
+ cx.scene.push_mouse_region(MouseRegion {
+ view_id: cx.current_view_id(),
+ discriminant,
+ bounds: visible_bounds,
+ handlers: HandlerSet::capture_all(),
+ hoverable: true,
+ });
cx.scene.pop_stacking_context();
}
self.child.paint(bounds.origin(), visible_bounds, cx);
@@ -167,15 +167,13 @@ impl Element for MouseEventHandler {
});
}
- cx.scene.push_mouse_region(
- MouseRegion::from_handlers(
- cx.current_view_id(),
- Some(self.discriminant),
- hit_bounds,
- self.handlers.clone(),
- )
- .with_hoverable(self.hoverable),
- );
+ cx.scene.push_mouse_region(MouseRegion {
+ view_id: cx.current_view_id(),
+ discriminant: self.discriminant,
+ bounds: hit_bounds,
+ handlers: self.handlers.clone(),
+ hoverable: self.hoverable,
+ });
self.child.paint(bounds.origin(), visible_bounds, cx);
}
@@ -1,4 +1,4 @@
-use std::ops::Range;
+use std::{any::TypeId, ops::Range};
use crate::{
geometry::{rect::RectF, vector::Vector2F},
@@ -78,10 +78,13 @@ impl Element for Overlay {
cx.scene.push_stacking_context(None);
if self.hoverable {
+ enum OverlayHoverCapture {}
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
bounds,
- ..Default::default()
+ discriminant: (TypeId::of::<OverlayHoverCapture>(), cx.current_view_id()),
+ handlers: Default::default(),
+ hoverable: true,
});
}
@@ -8,7 +8,8 @@ use crate::{
platform::{CursorStyle, Event},
scene::{
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent,
- HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
+ HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, ScrollWheelRegionEvent,
+ UpOutRegionEvent, UpRegionEvent,
},
text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
@@ -36,7 +37,7 @@ pub struct Presenter {
asset_cache: Arc<AssetCache>,
last_mouse_moved_event: Option<Event>,
hovered_region_ids: HashSet<MouseRegionId>,
- clicked_regions: Vec<MouseRegion>,
+ clicked_region_ids: HashSet<MouseRegionId>,
clicked_button: Option<MouseButton>,
mouse_position: Vector2F,
titlebar_height: f32,
@@ -61,7 +62,7 @@ impl Presenter {
asset_cache,
last_mouse_moved_event: None,
hovered_region_ids: Default::default(),
- clicked_regions: Vec::new(),
+ clicked_region_ids: Default::default(),
clicked_button: None,
mouse_position: vec2f(0., 0.),
titlebar_height,
@@ -75,7 +76,6 @@ impl Presenter {
) {
cx.start_frame();
for view_id in &invalidation.removed {
- invalidation.updated.remove(view_id);
self.rendered_views.remove(view_id);
}
for view_id in &invalidation.updated {
@@ -86,15 +86,9 @@ impl Presenter {
view_id: *view_id,
titlebar_height: self.titlebar_height,
hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self.clicked_button.map(|button| {
- (
- self.clicked_regions
- .iter()
- .filter_map(MouseRegion::id)
- .collect(),
- button,
- )
- }),
+ clicked_region_ids: self
+ .clicked_button
+ .map(|button| (self.clicked_region_ids.clone(), button)),
refreshing: false,
})
.unwrap(),
@@ -112,15 +106,9 @@ impl Presenter {
view_id: *view_id,
titlebar_height: self.titlebar_height,
hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self.clicked_button.map(|button| {
- (
- self.clicked_regions
- .iter()
- .filter_map(MouseRegion::id)
- .collect(),
- button,
- )
- }),
+ clicked_region_ids: self
+ .clicked_button
+ .map(|button| (self.clicked_region_ids.clone(), button)),
refreshing: true,
})
.unwrap();
@@ -184,15 +172,9 @@ impl Presenter {
view_stack: Vec::new(),
refreshing,
hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self.clicked_button.map(|button| {
- (
- self.clicked_regions
- .iter()
- .filter_map(MouseRegion::id)
- .collect(),
- button,
- )
- }),
+ clicked_region_ids: self
+ .clicked_button
+ .map(|button| (self.clicked_region_ids.clone(), button)),
titlebar_height: self.titlebar_height,
window_size,
app: cx,
@@ -248,14 +230,15 @@ impl Presenter {
// If there is already clicked_button stored, don't replace it.
if self.clicked_button.is_none() {
- self.clicked_regions = self
+ self.clicked_region_ids = self
.mouse_regions
.iter()
.filter_map(|(region, _)| {
- region
- .bounds
- .contains_point(e.position)
- .then(|| region.clone())
+ if region.bounds.contains_point(e.position) {
+ Some(region.id())
+ } else {
+ None
+ }
})
.collect();
self.clicked_button = Some(e.button);
@@ -341,6 +324,12 @@ impl Presenter {
self.last_mouse_moved_event = Some(event.clone());
}
+ Event::ScrollWheel(e) => {
+ events_to_send.push(MouseRegionEvent::ScrollWheel(ScrollWheelRegionEvent {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }))
+ }
_ => {}
}
@@ -375,23 +364,21 @@ impl Presenter {
top_most_depth = Some(depth);
}
- if let Some(region_id) = region.id() {
- // This unwrap relies on short circuiting boolean expressions
- // The right side of the && is only executed when contains_mouse
- // is true, and we know above that when contains_mouse is true
- // top_most_depth is set
- if contains_mouse && depth == top_most_depth.unwrap() {
- //Ensure that hover entrance events aren't sent twice
- if self.hovered_region_ids.insert(region_id) {
- valid_regions.push(region.clone());
- invalidated_views.insert(region.view_id);
- }
- } else {
- // Ensure that hover exit events aren't sent twice
- if self.hovered_region_ids.remove(®ion_id) {
- valid_regions.push(region.clone());
- invalidated_views.insert(region.view_id);
- }
+ // This unwrap relies on short circuiting boolean expressions
+ // The right side of the && is only executed when contains_mouse
+ // is true, and we know above that when contains_mouse is true
+ // top_most_depth is set
+ if contains_mouse && depth == top_most_depth.unwrap() {
+ //Ensure that hover entrance events aren't sent twice
+ if self.hovered_region_ids.insert(region.id()) {
+ valid_regions.push(region.clone());
+ invalidated_views.insert(region.view_id);
+ }
+ } else {
+ // Ensure that hover exit events aren't sent twice
+ if self.hovered_region_ids.remove(®ion.id()) {
+ valid_regions.push(region.clone());
+ invalidated_views.insert(region.view_id);
}
}
}
@@ -404,21 +391,23 @@ impl Presenter {
.unwrap_or(false)
{
// Clear clicked regions and clicked button
- let clicked_regions =
- std::mem::replace(&mut self.clicked_regions, Vec::new());
+ let clicked_region_ids =
+ std::mem::replace(&mut self.clicked_region_ids, Default::default());
self.clicked_button = None;
// Find regions which still overlap with the mouse since the last MouseDown happened
- for clicked_region in clicked_regions.into_iter().rev() {
- if clicked_region.bounds.contains_point(e.position) {
- valid_regions.push(clicked_region);
+ for (mouse_region, _) in self.mouse_regions.iter().rev() {
+ if clicked_region_ids.contains(&mouse_region.id()) {
+ valid_regions.push(mouse_region.clone());
}
}
}
}
MouseRegionEvent::Drag(_) => {
- for clicked_region in self.clicked_regions.iter().rev() {
- valid_regions.push(clicked_region.clone());
+ for (mouse_region, _) in self.mouse_regions.iter().rev() {
+ if self.clicked_region_ids.contains(&mouse_region.id()) {
+ valid_regions.push(mouse_region.clone());
+ }
}
}
@@ -447,10 +436,7 @@ impl Presenter {
region_event.set_region(valid_region.bounds);
if let MouseRegionEvent::Hover(e) = &mut region_event {
- e.started = valid_region
- .id()
- .map(|region_id| hovered_region_ids.contains(®ion_id))
- .unwrap_or(false)
+ e.started = hovered_region_ids.contains(&valid_region.id())
}
// Handle Down events if the MouseRegion has a Click handler. This makes the api more intuitive as you would
// not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
@@ -546,7 +532,7 @@ pub struct LayoutContext<'a> {
pub window_size: Vector2F,
titlebar_height: f32,
hovered_region_ids: HashSet<MouseRegionId>,
- clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
+ clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
}
impl<'a> LayoutContext<'a> {
@@ -536,11 +536,11 @@ impl ToJson for Border {
}
impl MouseRegion {
- pub fn id(&self) -> Option<MouseRegionId> {
- self.discriminant.map(|discriminant| MouseRegionId {
+ pub fn id(&self) -> MouseRegionId {
+ MouseRegionId {
view_id: self.view_id,
- discriminant,
- })
+ discriminant: self.discriminant,
+ }
}
}
@@ -6,50 +6,47 @@ use pathfinder_geometry::rect::RectF;
use crate::{EventContext, MouseButton};
-use super::mouse_region_event::{
- ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent,
- MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
+use super::{
+ mouse_region_event::{
+ ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent,
+ MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
+ },
+ ScrollWheelRegionEvent,
};
-#[derive(Clone, Default)]
+#[derive(Clone)]
pub struct MouseRegion {
pub view_id: usize,
- pub discriminant: Option<(TypeId, usize)>,
+ pub discriminant: (TypeId, usize),
pub bounds: RectF,
pub handlers: HandlerSet,
pub hoverable: bool,
}
impl MouseRegion {
- pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self {
- Self::from_handlers(view_id, discriminant, bounds, Default::default())
+ /// Region ID is used to track semantically equivalent mouse regions across render passes.
+ /// e.g. if you have mouse handlers attached to a list item type, then each item of the list
+ /// should pass a different (consistent) region_id. If you have one big region that covers your
+ /// whole component, just pass the view_id again.
+ pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
+ Self::from_handlers::<Tag>(view_id, region_id, bounds, Default::default())
}
- pub fn from_handlers(
- view_id: usize,
- discriminant: Option<(TypeId, usize)>,
- bounds: RectF,
- handlers: HandlerSet,
- ) -> Self {
- Self {
- view_id,
- discriminant,
- bounds,
- handlers,
- hoverable: true,
- }
+ pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
+ Self::from_handlers::<Tag>(view_id, region_id, bounds, HandlerSet::capture_all())
}
- pub fn handle_all(
+ pub fn from_handlers<Tag: 'static>(
view_id: usize,
- discriminant: Option<(TypeId, usize)>,
+ region_id: usize,
bounds: RectF,
+ handlers: HandlerSet,
) -> Self {
Self {
view_id,
- discriminant,
+ discriminant: (TypeId::of::<Tag>(), region_id),
bounds,
- handlers: HandlerSet::capture_all(),
+ handlers,
hoverable: true,
}
}
@@ -124,6 +121,14 @@ impl MouseRegion {
self
}
+ pub fn on_scroll(
+ mut self,
+ handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static,
+ ) -> Self {
+ self.handlers = self.handlers.on_scroll(handler);
+ self
+ }
+
pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
self.hoverable = is_hoverable;
self
@@ -345,4 +350,22 @@ impl HandlerSet {
}));
self
}
+
+ pub fn on_scroll(
+ mut self,
+ handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static,
+ ) -> Self {
+ self.set.insert((MouseRegionEvent::scroll_wheel_disc(), None),
+ Rc::new(move |region_event, cx| {
+ if let MouseRegionEvent::ScrollWheel(e) = region_event {
+ handler(e, cx);
+ } else {
+ panic!(
+ "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",
+ region_event
+ );
+ }
+ }));
+ self
+ }
}
@@ -168,7 +168,7 @@ impl MouseRegionEvent {
pub fn is_capturable(&self) -> bool {
match self {
MouseRegionEvent::Move(_) => true,
- MouseRegionEvent::Drag(_) => false,
+ MouseRegionEvent::Drag(_) => true,
MouseRegionEvent::Hover(_) => false,
MouseRegionEvent::Down(_) => true,
MouseRegionEvent::Up(_) => true,
@@ -29,6 +29,7 @@ pub struct Settings {
pub show_completions_on_input: bool,
pub vim_mode: bool,
pub autosave: Autosave,
+ pub default_dock_anchor: DockAnchor,
pub editor_defaults: EditorSettings,
pub editor_overrides: EditorSettings,
pub terminal_defaults: TerminalSettings,
@@ -150,6 +151,15 @@ pub enum WorkingDirectory {
Always { directory: String },
}
+#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum DockAnchor {
+ #[default]
+ Bottom,
+ Right,
+ Expanded,
+}
+
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
pub struct SettingsFileContent {
pub experiments: Option<FeatureFlags>,
@@ -167,6 +177,8 @@ pub struct SettingsFileContent {
pub vim_mode: Option<bool>,
#[serde(default)]
pub autosave: Option<Autosave>,
+ #[serde(default)]
+ pub default_dock_anchor: Option<DockAnchor>,
#[serde(flatten)]
pub editor: EditorSettings,
#[serde(default)]
@@ -216,6 +228,7 @@ impl Settings {
projects_online_by_default: defaults.projects_online_by_default.unwrap(),
vim_mode: defaults.vim_mode.unwrap(),
autosave: defaults.autosave.unwrap(),
+ default_dock_anchor: defaults.default_dock_anchor.unwrap(),
editor_defaults: EditorSettings {
tab_size: required(defaults.editor.tab_size),
hard_tabs: required(defaults.editor.hard_tabs),
@@ -268,6 +281,8 @@ impl Settings {
merge(&mut self.autosave, data.autosave);
merge(&mut self.experiments, data.experiments);
merge(&mut self.staff_mode, data.staff_mode);
+ merge(&mut self.default_dock_anchor, data.default_dock_anchor);
+
// Ensure terminal font is loaded, so we can request it in terminal_element layout
if let Some(terminal_font) = &data.terminal.font_family {
font_cache.load_family(&[terminal_font]).log_err();
@@ -336,6 +351,7 @@ impl Settings {
show_completions_on_input: true,
vim_mode: false,
autosave: Autosave::Off,
+ default_dock_anchor: DockAnchor::Bottom,
editor_defaults: EditorSettings {
tab_size: Some(4.try_into().unwrap()),
hard_tabs: Some(false),
@@ -366,7 +366,7 @@ impl TerminalElement {
) {
let connection = self.terminal;
- let mut region = MouseRegion::new(view_id, None, visible_bounds);
+ let mut region = MouseRegion::new::<Self>(view_id, view_id, visible_bounds);
// Terminal Emulator controlled behavior:
region = region
@@ -57,7 +57,7 @@ pub struct Workspace {
pub notifications: Notifications,
pub joining_project_avatar: ImageStyle,
pub joining_project_message: ContainedText,
- pub fullscreen_dock: ContainerStyle,
+ pub dock: Dock,
}
#[derive(Clone, Deserialize, Default)]
@@ -150,6 +150,14 @@ pub struct Toolbar {
pub nav_button: Interactive<IconButton>,
}
+#[derive(Clone, Deserialize, Default)]
+pub struct Dock {
+ pub wash_color: Color,
+ pub flex: f32,
+ pub panel: ContainerStyle,
+ pub maximized: ContainerStyle,
+}
+
#[derive(Clone, Deserialize, Default)]
pub struct Notifications {
#[serde(flatten)]
@@ -1,14 +1,14 @@
use gpui::{
actions,
- elements::{ChildView, MouseEventHandler, Svg},
+ elements::{ChildView, Container, FlexItem, Margin, MouseEventHandler, Svg},
impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton,
- MutableAppContext, View, ViewContext, ViewHandle, WeakViewHandle,
+ MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
};
use serde::Deserialize;
-use settings::Settings;
+use settings::{DockAnchor, Settings};
use theme::Theme;
-use crate::{pane, ItemHandle, Pane, StatusItemView, Workspace};
+use crate::{ItemHandle, Pane, StatusItemView, Workspace};
#[derive(PartialEq, Clone, Deserialize)]
pub struct MoveDock(pub DockAnchor);
@@ -24,14 +24,6 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Dock::move_dock);
}
-#[derive(PartialEq, Eq, Default, Copy, Clone, Deserialize)]
-pub enum DockAnchor {
- #[default]
- Bottom,
- Right,
- Expanded,
-}
-
#[derive(Copy, Clone)]
pub enum DockPosition {
Shown(DockAnchor),
@@ -79,63 +71,56 @@ pub struct Dock {
impl Dock {
pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
let pane = cx.add_view(|cx| Pane::new(true, cx));
-
- cx.subscribe(&pane.clone(), |workspace, _, event, cx| {
- if let pane::Event::Remove = event {
- workspace.dock.hide();
- cx.notify();
- }
+ let pane_id = pane.id();
+ cx.subscribe(&pane, move |workspace, _, event, cx| {
+ workspace.handle_pane_event(pane_id, event, cx);
})
.detach();
Self {
pane,
- position: Default::default(),
+ position: DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor),
default_item_factory,
}
}
- pub fn pane(&self) -> ViewHandle<Pane> {
- self.pane.clone()
+ pub fn pane(&self) -> &ViewHandle<Pane> {
+ &self.pane
}
- fn hide(&mut self) {
- self.position = self.position.hide();
+ pub fn visible_pane(&self) -> Option<&ViewHandle<Pane>> {
+ self.position.visible().map(|_| self.pane())
}
- fn ensure_not_empty(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- let pane = workspace.dock.pane.clone();
- if pane.read(cx).items().next().is_none() {
- let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
- Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
+ fn set_dock_position(
+ workspace: &mut Workspace,
+ new_position: DockPosition,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ workspace.dock.position = new_position;
+ let now_visible = workspace.dock.visible_pane().is_some();
+ if now_visible {
+ // Ensure that the pane has at least one item or construct a default item to put in it
+ let pane = workspace.dock.pane.clone();
+ if pane.read(cx).items().next().is_none() {
+ let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
+ Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
+ }
+ cx.focus(pane);
+ } else {
+ if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() {
+ cx.focus(last_active_center_pane);
+ }
}
+ cx.notify();
+ }
+
+ pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+ Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
}
fn toggle(workspace: &mut Workspace, _: &ToggleDock, cx: &mut ViewContext<Workspace>) {
- // Shift-escape ON
- // Get or insert the dock's last focused terminal
- // Open the dock in fullscreen
- // Focus that terminal
-
- // Shift-escape OFF
- // Close the dock
- // Return focus to center
-
- // Behaviors:
- // If the dock is shown, hide it
- // If the dock is hidden, show it
- // If the dock was full screen, open it in last position (bottom or right)
- // If the dock was bottom or right, re-open it in that context (and with the previous % width)
-
- workspace.dock.position = workspace.dock.position.toggle();
- if workspace.dock.position.visible().is_some() {
- Self::ensure_not_empty(workspace, cx);
- cx.focus(workspace.dock.pane.clone());
- } else {
- cx.focus_self();
- }
- cx.notify();
- workspace.status_bar().update(cx, |_, cx| cx.notify());
+ Self::set_dock_position(workspace, workspace.dock.position.toggle(), cx);
}
fn move_dock(
@@ -143,17 +128,54 @@ impl Dock {
&MoveDock(new_anchor): &MoveDock,
cx: &mut ViewContext<Workspace>,
) {
- // Clear the previous position if the dock is not visible.
- workspace.dock.position = DockPosition::Shown(new_anchor);
- Self::ensure_not_empty(workspace, cx);
- cx.notify();
+ Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
}
- pub fn render(&self, _theme: &Theme, anchor: DockAnchor) -> Option<ElementBox> {
+ pub fn render(
+ &self,
+ theme: &Theme,
+ anchor: DockAnchor,
+ cx: &mut RenderContext<Workspace>,
+ ) -> Option<ElementBox> {
+ let style = &theme.workspace.dock;
self.position
.visible()
.filter(|current_anchor| *current_anchor == anchor)
- .map(|_| ChildView::new(self.pane.clone()).boxed())
+ .map(|anchor| match anchor {
+ DockAnchor::Bottom | DockAnchor::Right => {
+ let mut panel_style = style.panel.clone();
+ if anchor == DockAnchor::Bottom {
+ panel_style.margin = Margin {
+ top: panel_style.margin.top,
+ ..Default::default()
+ };
+ } else {
+ panel_style.margin = Margin {
+ left: panel_style.margin.left,
+ ..Default::default()
+ };
+ }
+ FlexItem::new(
+ Container::new(ChildView::new(self.pane.clone()).boxed())
+ .with_style(style.panel)
+ .boxed(),
+ )
+ .flex(style.flex, true)
+ .boxed()
+ }
+ DockAnchor::Expanded => Container::new(
+ MouseEventHandler::new::<Dock, _, _>(0, cx, |_state, _cx| {
+ Container::new(ChildView::new(self.pane.clone()).boxed())
+ .with_style(style.maximized)
+ .boxed()
+ })
+ .capture_all()
+ .with_cursor_style(CursorStyle::Arrow)
+ .boxed(),
+ )
+ .with_background_color(style.wash_color)
+ .boxed(),
+ })
}
}
@@ -1,8 +1,7 @@
use super::{ItemHandle, SplitDirection};
use crate::{
- dock::{DockAnchor, MoveDock},
- toolbar::Toolbar,
- Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
+ dock::MoveDock, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle,
+ Workspace,
};
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
@@ -25,7 +24,7 @@ use gpui::{
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
-use settings::{Autosave, Settings};
+use settings::{Autosave, DockAnchor, Settings};
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
use theme::Theme;
use util::ResultExt;
@@ -187,6 +186,7 @@ pub fn init(cx: &mut MutableAppContext) {
});
}
+#[derive(Debug)]
pub enum Event {
Focused,
ActivateItem { local: bool },
@@ -1392,7 +1392,7 @@ impl View for Pane {
.with_cursor_style(CursorStyle::PointingHand)
.on_down(MouseButton::Left, |e, cx| {
cx.dispatch_action(DeployNewMenu {
- position: e.position,
+ position: e.region.lower_right(),
});
})
.boxed(),
@@ -1422,11 +1422,11 @@ impl View for Pane {
.on_down(MouseButton::Left, move |e, cx| {
if is_dock {
cx.dispatch_action(DeployDockMenu {
- position: e.position,
+ position: e.region.lower_right(),
});
} else {
cx.dispatch_action(DeploySplitMenu {
- position: e.position,
+ position: e.region.lower_right(),
});
}
})
@@ -1613,7 +1613,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
+ let (_, workspace) =
+ cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@@ -1701,7 +1702,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
+ let (_, workspace) =
+ cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@@ -1777,7 +1779,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
+ let (_, workspace) =
+ cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// singleton view
@@ -18,7 +18,7 @@ use client::{
};
use clock::ReplicaId;
use collections::{hash_map, HashMap, HashSet};
-use dock::{DefaultItemFactory, Dock, DockAnchor, ToggleDockButton};
+use dock::{DefaultItemFactory, Dock, ToggleDockButton};
use drag_and_drop::DragAndDrop;
use futures::{channel::oneshot, FutureExt};
use gpui::{
@@ -41,7 +41,7 @@ use postage::prelude::Stream;
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
use searchable::SearchableItemHandle;
use serde::Deserialize;
-use settings::{Autosave, Settings};
+use settings::{Autosave, DockAnchor, Settings};
use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem};
use smallvec::SmallVec;
use status_bar::StatusBar;
@@ -892,6 +892,7 @@ pub struct Workspace {
panes: Vec<ViewHandle<Pane>>,
panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
active_pane: ViewHandle<Pane>,
+ last_active_center_pane: Option<ViewHandle<Pane>>,
status_bar: ViewHandle<StatusBar>,
dock: Dock,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
@@ -987,6 +988,7 @@ impl Workspace {
cx.emit_global(WorkspaceCreated(weak_self.clone()));
let dock = Dock::new(cx, dock_default_factory);
+ let dock_pane = dock.pane().clone();
let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left));
let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right));
@@ -1011,9 +1013,10 @@ impl Workspace {
weak_self,
center: PaneGroup::new(center_pane.clone()),
dock,
- panes: vec![center_pane.clone()],
+ panes: vec![center_pane.clone(), dock_pane],
panes_by_item: Default::default(),
active_pane: center_pane.clone(),
+ last_active_center_pane: Some(center_pane.clone()),
status_bar,
notifications: Default::default(),
client,
@@ -1556,10 +1559,6 @@ impl Workspace {
Pane::add_item(self, &active_pane, item, true, true, None, cx);
}
- pub fn add_item_to_dock(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
- Pane::add_item(self, &self.dock.pane(), item, true, true, None, cx);
- }
-
pub fn open_path(
&mut self,
path: impl Into<ProjectPath>,
@@ -1696,6 +1695,10 @@ impl Workspace {
status_bar.set_active_pane(&self.active_pane, cx);
});
self.active_item_path_changed(cx);
+
+ if &pane != self.dock.pane() {
+ self.last_active_center_pane = Some(pane.clone());
+ }
cx.notify();
}
@@ -1715,21 +1718,19 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) {
if let Some(pane) = self.pane(pane_id) {
+ let is_dock = &pane == self.dock.pane();
match event {
- pane::Event::Split(direction) => {
+ pane::Event::Split(direction) if !is_dock => {
self.split_pane(pane, *direction, cx);
}
- pane::Event::Remove => {
- self.remove_pane(pane, cx);
- }
- pane::Event::Focused => {
- self.handle_pane_focused(pane, cx);
- }
+ pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
+ pane::Event::Remove if is_dock => Dock::hide(self, cx),
+ pane::Event::Focused => self.handle_pane_focused(pane, cx),
pane::Event::ActivateItem { local } => {
if *local {
self.unfollow(&pane, cx);
}
- if pane == self.active_pane {
+ if &pane == self.active_pane() {
self.active_item_path_changed(cx);
}
}
@@ -1747,8 +1748,9 @@ impl Workspace {
}
}
}
+ _ => {}
}
- } else {
+ } else if self.dock.visible_pane().is_none() {
error!("pane {} not found", pane_id);
}
}
@@ -1779,6 +1781,10 @@ impl Workspace {
for removed_item in pane.read(cx).items() {
self.panes_by_item.remove(&removed_item.id());
}
+ if self.last_active_center_pane == Some(pane) {
+ self.last_active_center_pane = None;
+ }
+
cx.notify();
} else {
self.active_item_path_changed(cx);
@@ -1797,6 +1803,10 @@ impl Workspace {
&self.active_pane
}
+ pub fn dock_pane(&self) -> &ViewHandle<Pane> {
+ self.dock.pane()
+ }
+
fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
if let Some(remote_id) = remote_id {
self.remote_entity_subscription =
@@ -2582,25 +2592,17 @@ impl View for Workspace {
.flex(1., true)
.boxed(),
)
- .with_children(
- self.dock
- .render(&theme, DockAnchor::Bottom)
- .map(|dock| {
- FlexItem::new(dock)
- .flex(1., true)
- .boxed()
- }),
- )
+ .with_children(self.dock.render(
+ &theme,
+ DockAnchor::Bottom,
+ cx,
+ ))
.boxed(),
)
.flex(1., true)
.boxed(),
)
- .with_children(
- self.dock
- .render(&theme, DockAnchor::Right)
- .map(|dock| FlexItem::new(dock).flex(1., true).boxed()),
- )
+ .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
.with_children(
if self.right_sidebar.read(cx).active_item().is_some() {
Some(
@@ -2614,13 +2616,7 @@ impl View for Workspace {
)
.boxed()
})
- .with_children(self.dock.render(&theme, DockAnchor::Expanded).map(
- |dock| {
- Container::new(dock)
- .with_style(theme.workspace.fullscreen_dock)
- .boxed()
- },
- ))
+ .with_children(self.dock.render(&theme, DockAnchor::Expanded, cx))
.with_children(self.modal.as_ref().map(|m| {
ChildView::new(m)
.contained()
@@ -243,6 +243,7 @@ pub fn initialize_workspace(
.detach();
cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
+ cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone()));
let settings = cx.global::<Settings>();
@@ -156,9 +156,17 @@ export default function workspace(theme: Theme) {
width: 400,
margin: { right: 10, bottom: 10 },
},
- fullscreenDock: {
- background: withOpacity(theme.backgroundColor[500].base, 0.8),
- padding: 25,
+ dock: {
+ wash_color: withOpacity(theme.backgroundColor[500].base, 0.5),
+ flex: 0.5,
+ panel: {
+ margin: 4,
+ },
+ maximized: {
+ margin: 32,
+ border: border(theme, "secondary"),
+ shadow: modalShadow(theme),
+ }
}
};
}