Merge pull request #889 from zed-industries/cursor-style-revamp

Apply cursor styles during paint and make editor's cursor an I-Beam
This commit is contained in:
Nathan Sobo 2022-04-22 13:20:31 -06:00 committed by GitHub
commit ddc45eb24e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 124 additions and 96 deletions

View File

@ -89,10 +89,6 @@
"top": 7 "top": 7
} }
}, },
"margin": {
"bottom": 52,
"top": 52
},
"shadow": { "shadow": {
"blur": 16, "blur": 16,
"color": "#00000052", "color": "#00000052",
@ -158,6 +154,13 @@
"right": 8 "right": 8
} }
}, },
"modal": {
"margin": {
"bottom": 52,
"top": 52
},
"cursor": "Arrow"
},
"left_sidebar": { "left_sidebar": {
"width": 30, "width": 30,
"background": "#1c1c1c", "background": "#1c1c1c",

View File

@ -89,10 +89,6 @@
"top": 7 "top": 7
} }
}, },
"margin": {
"bottom": 52,
"top": 52
},
"shadow": { "shadow": {
"blur": 16, "blur": 16,
"color": "#0000001f", "color": "#0000001f",
@ -158,6 +154,13 @@
"right": 8 "right": 8
} }
}, },
"modal": {
"margin": {
"bottom": 52,
"top": 52
},
"cursor": "Arrow"
},
"left_sidebar": { "left_sidebar": {
"width": 30, "width": 30,
"background": "#f8f8f8", "background": "#f8f8f8",

View File

@ -16,6 +16,7 @@ use gpui::{
PathBuilder, PathBuilder,
}, },
json::{self, ToJson}, json::{self, ToJson},
platform::CursorStyle,
text_layout::{self, Line, RunStyle, TextLayoutCache}, text_layout::{self, Line, RunStyle, TextLayoutCache},
AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext, AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
@ -329,6 +330,7 @@ impl EditorElement {
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
cx.scene.push_layer(Some(bounds)); cx.scene.push_layer(Some(bounds));
cx.scene.push_cursor_style(bounds, CursorStyle::IBeam);
for (range, color) in &layout.highlighted_ranges { for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range( self.paint_highlighted_range(

View File

@ -161,7 +161,6 @@ impl View for GoToLine {
self.max_point.row + 1 self.max_point.row + 1
); );
Align::new(
ConstrainedBox::new( ConstrainedBox::new(
Container::new( Container::new(
Flex::new(Axis::Vertical) Flex::new(Axis::Vertical)
@ -181,9 +180,6 @@ impl View for GoToLine {
.boxed(), .boxed(),
) )
.with_max_width(500.0) .with_max_width(500.0)
.boxed(),
)
.top()
.named("go to line") .named("go to line")
} }

View File

@ -4,7 +4,7 @@ use crate::{
elements::ElementBox, elements::ElementBox,
executor::{self, Task}, executor::{self, Task},
keymap::{self, Binding, Keystroke}, keymap::{self, Binding, Keystroke},
platform::{self, CursorStyle, Platform, PromptLevel, WindowOptions}, platform::{self, Platform, PromptLevel, WindowOptions},
presenter::Presenter, presenter::Presenter,
util::post_inc, util::post_inc,
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache, AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
@ -31,10 +31,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin, pin::Pin,
rc::{self, Rc}, rc::{self, Rc},
sync::{ sync::{Arc, Weak},
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak,
},
time::Duration, time::Duration,
}; };
@ -766,7 +763,6 @@ pub struct MutableAppContext {
pending_global_notifications: HashSet<TypeId>, pending_global_notifications: HashSet<TypeId>,
pending_flushes: usize, pending_flushes: usize,
flushing_effects: bool, flushing_effects: bool,
next_cursor_style_handle_id: Arc<AtomicUsize>,
halt_action_dispatch: bool, halt_action_dispatch: bool,
} }
@ -818,7 +814,6 @@ impl MutableAppContext {
pending_global_notifications: HashSet::new(), pending_global_notifications: HashSet::new(),
pending_flushes: 0, pending_flushes: 0,
flushing_effects: false, flushing_effects: false,
next_cursor_style_handle_id: Default::default(),
halt_action_dispatch: false, halt_action_dispatch: false,
} }
} }
@ -1949,16 +1944,6 @@ impl MutableAppContext {
self.presenters_and_platform_windows = presenters; self.presenters_and_platform_windows = presenters;
} }
pub fn set_cursor_style(&mut self, style: CursorStyle) -> CursorStyleHandle {
self.platform.set_cursor_style(style);
let id = self.next_cursor_style_handle_id.fetch_add(1, SeqCst);
CursorStyleHandle {
id,
next_cursor_style_handle_id: self.next_cursor_style_handle_id.clone(),
platform: self.platform(),
}
}
fn handle_subscription_effect( fn handle_subscription_effect(
&mut self, &mut self,
entity_id: usize, entity_id: usize,
@ -4452,20 +4437,6 @@ impl<T> Drop for ElementStateHandle<T> {
} }
} }
pub struct CursorStyleHandle {
id: usize,
next_cursor_style_handle_id: Arc<AtomicUsize>,
platform: Arc<dyn Platform>,
}
impl Drop for CursorStyleHandle {
fn drop(&mut self) {
if self.id + 1 == self.next_cursor_style_handle_id.load(SeqCst) {
self.platform.set_cursor_style(CursorStyle::Arrow);
}
}
}
#[must_use] #[must_use]
pub enum Subscription { pub enum Subscription {
Subscription { Subscription {

View File

@ -1,17 +1,17 @@
use pathfinder_geometry::rect::RectF;
use serde::Deserialize;
use serde_json::json;
use crate::{ use crate::{
color::Color, color::Color,
geometry::{ geometry::{
deserialize_vec2f, deserialize_vec2f,
rect::RectF,
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::ToJson, json::ToJson,
platform::CursorStyle,
scene::{self, Border, Quad}, scene::{self, Border, Quad},
Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
}; };
use serde::Deserialize;
use serde_json::json;
#[derive(Clone, Copy, Debug, Default, Deserialize)] #[derive(Clone, Copy, Debug, Default, Deserialize)]
pub struct ContainerStyle { pub struct ContainerStyle {
@ -27,6 +27,8 @@ pub struct ContainerStyle {
pub corner_radius: f32, pub corner_radius: f32,
#[serde(default)] #[serde(default)]
pub shadow: Option<Shadow>, pub shadow: Option<Shadow>,
#[serde(default)]
pub cursor: Option<CursorStyle>,
} }
pub struct Container { pub struct Container {
@ -128,6 +130,11 @@ impl Container {
self self
} }
pub fn with_cursor(mut self, style: CursorStyle) -> Self {
self.style.cursor = Some(style);
self
}
fn margin_size(&self) -> Vector2F { fn margin_size(&self) -> Vector2F {
vec2f( vec2f(
self.style.margin.left + self.style.margin.right, self.style.margin.left + self.style.margin.right,
@ -205,6 +212,10 @@ impl Element for Container {
}); });
} }
if let Some(style) = self.style.cursor {
cx.scene.push_cursor_style(quad_bounds, style);
}
let child_origin = let child_origin =
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top); quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);

View File

@ -5,8 +5,8 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
platform::CursorStyle, platform::CursorStyle,
CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
Event, EventContext, LayoutContext, PaintContext, SizeConstraint, EventContext, LayoutContext, PaintContext, SizeConstraint,
}; };
use serde_json::json; use serde_json::json;
@ -25,7 +25,6 @@ pub struct MouseState {
pub hovered: bool, pub hovered: bool,
pub clicked: bool, pub clicked: bool,
prev_drag_position: Option<Vector2F>, prev_drag_position: Option<Vector2F>,
cursor_style_handle: Option<CursorStyleHandle>,
} }
impl MouseEventHandler { impl MouseEventHandler {
@ -72,6 +71,14 @@ impl MouseEventHandler {
self.padding = padding; self.padding = padding;
self self
} }
fn hit_bounds(&self, bounds: RectF) -> RectF {
RectF::from_points(
bounds.origin() - vec2f(self.padding.left, self.padding.top),
bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
)
.round_out()
}
} }
impl Element for MouseEventHandler { impl Element for MouseEventHandler {
@ -93,6 +100,10 @@ impl Element for MouseEventHandler {
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, cx: &mut PaintContext,
) -> Self::PaintState { ) -> Self::PaintState {
if let Some(cursor_style) = self.cursor_style {
cx.scene
.push_cursor_style(self.hit_bounds(bounds), cursor_style);
}
self.child.paint(bounds.origin(), visible_bounds, cx); self.child.paint(bounds.origin(), visible_bounds, cx);
} }
@ -105,19 +116,13 @@ impl Element for MouseEventHandler {
_: &mut Self::PaintState, _: &mut Self::PaintState,
cx: &mut EventContext, cx: &mut EventContext,
) -> bool { ) -> bool {
let cursor_style = self.cursor_style; let hit_bounds = self.hit_bounds(visible_bounds);
let mouse_down_handler = self.mouse_down_handler.as_mut(); let mouse_down_handler = self.mouse_down_handler.as_mut();
let click_handler = self.click_handler.as_mut(); let click_handler = self.click_handler.as_mut();
let drag_handler = self.drag_handler.as_mut(); let drag_handler = self.drag_handler.as_mut();
let handled_in_child = self.child.dispatch_event(event, cx); let handled_in_child = self.child.dispatch_event(event, cx);
let hit_bounds = RectF::from_points(
visible_bounds.origin() - vec2f(self.padding.left, self.padding.top),
visible_bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
)
.round_out();
self.state.update(cx, |state, cx| match event { self.state.update(cx, |state, cx| match event {
Event::MouseMoved { Event::MouseMoved {
position, position,
@ -127,16 +132,6 @@ impl Element for MouseEventHandler {
let mouse_in = hit_bounds.contains_point(*position); let mouse_in = hit_bounds.contains_point(*position);
if state.hovered != mouse_in { if state.hovered != mouse_in {
state.hovered = mouse_in; state.hovered = mouse_in;
if let Some(cursor_style) = cursor_style {
if !state.clicked {
if state.hovered {
state.cursor_style_handle =
Some(cx.set_cursor_style(cursor_style));
} else {
state.cursor_style_handle = None;
}
}
}
cx.notify(); cx.notify();
return true; return true;
} }
@ -160,9 +155,6 @@ impl Element for MouseEventHandler {
state.prev_drag_position = None; state.prev_drag_position = None;
if !handled_in_child && state.clicked { if !handled_in_child && state.clicked {
state.clicked = false; state.clicked = false;
if !state.hovered {
state.cursor_style_handle = None;
}
cx.notify(); cx.notify();
if let Some(handler) = click_handler { if let Some(handler) = click_handler {
if hit_bounds.contains_point(*position) { if hit_bounds.contains_point(*position) {

View File

@ -21,6 +21,7 @@ use anyhow::{anyhow, Result};
use async_task::Runnable; use async_task::Runnable;
pub use event::{Event, NavigationDirection}; pub use event::{Event, NavigationDirection};
use postage::oneshot; use postage::oneshot;
use serde::Deserialize;
use std::{ use std::{
any::Any, any::Any,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -125,11 +126,12 @@ pub enum PromptLevel {
Critical, Critical,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Deserialize)]
pub enum CursorStyle { pub enum CursorStyle {
Arrow, Arrow,
ResizeLeftRight, ResizeLeftRight,
PointingHand, PointingHand,
IBeam,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]

View File

@ -583,6 +583,7 @@ impl platform::Platform for MacPlatform {
CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor], CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
}; };
let _: () = msg_send![cursor, set]; let _: () = msg_send![cursor, set];
} }

View File

@ -4,7 +4,7 @@ use crate::{
font_cache::FontCache, font_cache::FontCache,
geometry::rect::RectF, geometry::rect::RectF,
json::{self, ToJson}, json::{self, ToJson},
platform::Event, platform::{CursorStyle, Event},
text_layout::TextLayoutCache, text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox,
ElementStateContext, Entity, FontSystem, ModelHandle, ReadModel, ReadView, Scene, ElementStateContext, Entity, FontSystem, ModelHandle, ReadModel, ReadView, Scene,
@ -22,6 +22,7 @@ pub struct Presenter {
window_id: usize, window_id: usize,
pub(crate) rendered_views: HashMap<usize, ElementBox>, pub(crate) rendered_views: HashMap<usize, ElementBox>,
parents: HashMap<usize, usize>, parents: HashMap<usize, usize>,
cursor_styles: Vec<(RectF, CursorStyle)>,
font_cache: Arc<FontCache>, font_cache: Arc<FontCache>,
text_layout_cache: TextLayoutCache, text_layout_cache: TextLayoutCache,
asset_cache: Arc<AssetCache>, asset_cache: Arc<AssetCache>,
@ -42,6 +43,7 @@ impl Presenter {
window_id, window_id,
rendered_views: cx.render_views(window_id, titlebar_height), rendered_views: cx.render_views(window_id, titlebar_height),
parents: HashMap::new(), parents: HashMap::new(),
cursor_styles: Default::default(),
font_cache, font_cache,
text_layout_cache, text_layout_cache,
asset_cache, asset_cache,
@ -118,6 +120,7 @@ impl Presenter {
RectF::new(Vector2F::zero(), window_size), RectF::new(Vector2F::zero(), window_size),
); );
self.text_layout_cache.finish_frame(); self.text_layout_cache.finish_frame();
self.cursor_styles = scene.cursor_styles();
if let Some(event) = self.last_mouse_moved_event.clone() { if let Some(event) = self.last_mouse_moved_event.clone() {
self.dispatch_event(event, cx) self.dispatch_event(event, cx)
@ -171,8 +174,21 @@ impl Presenter {
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) { pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) { if let Some(root_view_id) = cx.root_view_id(self.window_id) {
match event { match event {
Event::MouseMoved { .. } => { Event::MouseMoved {
position,
left_mouse_down,
} => {
self.last_mouse_moved_event = Some(event.clone()); self.last_mouse_moved_event = Some(event.clone());
if !left_mouse_down {
cx.platform().set_cursor_style(CursorStyle::Arrow);
for (bounds, style) in self.cursor_styles.iter().rev() {
if bounds.contains_point(position) {
cx.platform().set_cursor_style(*style);
break;
}
}
}
} }
Event::LeftMouseDragged { position } => { Event::LeftMouseDragged { position } => {
self.last_mouse_moved_event = Some(Event::MouseMoved { self.last_mouse_moved_event = Some(Event::MouseMoved {

View File

@ -7,6 +7,7 @@ use crate::{
fonts::{FontId, GlyphId}, fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::ToJson, json::ToJson,
platform::CursorStyle,
ImageData, ImageData,
}; };
@ -32,6 +33,7 @@ pub struct Layer {
image_glyphs: Vec<ImageGlyph>, image_glyphs: Vec<ImageGlyph>,
icons: Vec<Icon>, icons: Vec<Icon>,
paths: Vec<Path>, paths: Vec<Path>,
cursor_styles: Vec<(RectF, CursorStyle)>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -173,6 +175,13 @@ impl Scene {
self.stacking_contexts.iter().flat_map(|s| &s.layers) self.stacking_contexts.iter().flat_map(|s| &s.layers)
} }
pub fn cursor_styles(&self) -> Vec<(RectF, CursorStyle)> {
self.layers()
.flat_map(|layer| &layer.cursor_styles)
.copied()
.collect()
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) { pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context_stack self.active_stacking_context_stack
.push(self.stacking_contexts.len()); .push(self.stacking_contexts.len());
@ -197,6 +206,10 @@ impl Scene {
self.active_layer().push_quad(quad) self.active_layer().push_quad(quad)
} }
pub fn push_cursor_style(&mut self, bounds: RectF, style: CursorStyle) {
self.active_layer().push_cursor_style(bounds, style);
}
pub fn push_image(&mut self, image: Image) { pub fn push_image(&mut self, image: Image) {
self.active_layer().push_image(image) self.active_layer().push_image(image)
} }
@ -285,6 +298,7 @@ impl Layer {
glyphs: Default::default(), glyphs: Default::default(),
icons: Default::default(), icons: Default::default(),
paths: Default::default(), paths: Default::default(),
cursor_styles: Default::default(),
} }
} }
@ -302,6 +316,14 @@ impl Layer {
self.quads.as_slice() self.quads.as_slice()
} }
fn push_cursor_style(&mut self, bounds: RectF, style: CursorStyle) {
if let Some(bounds) = bounds.intersection(self.clip_bounds.unwrap_or(bounds)) {
if can_draw(bounds) {
self.cursor_styles.push((bounds, style));
}
}
}
fn push_underline(&mut self, underline: Underline) { fn push_underline(&mut self, underline: Underline) {
if underline.width > 0. { if underline.width > 0. {
self.underlines.push(underline); self.underlines.push(underline);

View File

@ -99,8 +99,6 @@ impl<D: PickerDelegate> View for Picker<D> {
.constrained() .constrained()
.with_max_width(self.max_size.x()) .with_max_width(self.max_size.x())
.with_max_height(self.max_size.y()) .with_max_height(self.max_size.y())
.aligned()
.top()
.named("picker") .named("picker")
} }

View File

@ -43,6 +43,7 @@ pub struct Workspace {
pub status_bar: StatusBar, pub status_bar: StatusBar,
pub toolbar: Toolbar, pub toolbar: Toolbar,
pub disconnected_overlay: ContainedText, pub disconnected_overlay: ContainedText,
pub modal: ContainerStyle,
} }
#[derive(Clone, Deserialize, Default)] #[derive(Clone, Deserialize, Default)]

View File

@ -1983,7 +1983,14 @@ impl View for Workspace {
content.add_child(self.right_sidebar.render(&theme, cx)); content.add_child(self.right_sidebar.render(&theme, cx));
content.boxed() content.boxed()
}) })
.with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed())) .with_children(self.modal.as_ref().map(|m| {
ChildView::new(m)
.contained()
.with_style(theme.workspace.modal)
.aligned()
.top()
.boxed()
}))
.flex(1.0, true) .flex(1.0, true)
.boxed(), .boxed(),
) )

View File

@ -50,10 +50,6 @@ export default function selectorModal(theme: Theme): Object {
top: 7, top: 7,
}, },
}, },
margin: {
bottom: 52,
top: 52,
},
shadow: shadow(theme), shadow: shadow(theme),
}; };
} }

View File

@ -75,6 +75,13 @@ export default function workspace(theme: Theme) {
leaderBorderWidth: 2.0, leaderBorderWidth: 2.0,
tab, tab,
activeTab, activeTab,
modal: {
margin: {
bottom: 52,
top: 52,
},
cursor: "Arrow"
},
leftSidebar: { leftSidebar: {
...sidebar, ...sidebar,
border: border(theme, "primary", { right: true }), border: border(theme, "primary", { right: true }),