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

View File

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

View File

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

View File

@ -161,29 +161,25 @@ impl View for GoToLine {
self.max_point.row + 1
);
Align::new(
ConstrainedBox::new(
Container::new(
Flex::new(Axis::Vertical)
.with_child(
Container::new(ChildView::new(&self.line_editor).boxed())
.with_style(theme.input_editor.container)
.boxed(),
)
.with_child(
Container::new(Label::new(label, theme.empty.label.clone()).boxed())
.with_style(theme.empty.container)
.boxed(),
)
.boxed(),
)
.with_style(theme.container)
.boxed(),
ConstrainedBox::new(
Container::new(
Flex::new(Axis::Vertical)
.with_child(
Container::new(ChildView::new(&self.line_editor).boxed())
.with_style(theme.input_editor.container)
.boxed(),
)
.with_child(
Container::new(Label::new(label, theme.empty.label.clone()).boxed())
.with_style(theme.empty.container)
.boxed(),
)
.boxed(),
)
.with_max_width(500.0)
.with_style(theme.container)
.boxed(),
)
.top()
.with_max_width(500.0)
.named("go to line")
}

View File

@ -4,7 +4,7 @@ use crate::{
elements::ElementBox,
executor::{self, Task},
keymap::{self, Binding, Keystroke},
platform::{self, CursorStyle, Platform, PromptLevel, WindowOptions},
platform::{self, Platform, PromptLevel, WindowOptions},
presenter::Presenter,
util::post_inc,
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
@ -31,10 +31,7 @@ use std::{
path::{Path, PathBuf},
pin::Pin,
rc::{self, Rc},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak,
},
sync::{Arc, Weak},
time::Duration,
};
@ -766,7 +763,6 @@ pub struct MutableAppContext {
pending_global_notifications: HashSet<TypeId>,
pending_flushes: usize,
flushing_effects: bool,
next_cursor_style_handle_id: Arc<AtomicUsize>,
halt_action_dispatch: bool,
}
@ -818,7 +814,6 @@ impl MutableAppContext {
pending_global_notifications: HashSet::new(),
pending_flushes: 0,
flushing_effects: false,
next_cursor_style_handle_id: Default::default(),
halt_action_dispatch: false,
}
}
@ -1949,16 +1944,6 @@ impl MutableAppContext {
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(
&mut self,
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]
pub enum Subscription {
Subscription {

View File

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

View File

@ -5,8 +5,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
platform::CursorStyle,
CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle,
Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use serde_json::json;
@ -25,7 +25,6 @@ pub struct MouseState {
pub hovered: bool,
pub clicked: bool,
prev_drag_position: Option<Vector2F>,
cursor_style_handle: Option<CursorStyleHandle>,
}
impl MouseEventHandler {
@ -72,6 +71,14 @@ impl MouseEventHandler {
self.padding = padding;
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 {
@ -93,6 +100,10 @@ impl Element for MouseEventHandler {
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> 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);
}
@ -105,19 +116,13 @@ impl Element for MouseEventHandler {
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> 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 click_handler = self.click_handler.as_mut();
let drag_handler = self.drag_handler.as_mut();
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 {
Event::MouseMoved {
position,
@ -127,16 +132,6 @@ impl Element for MouseEventHandler {
let mouse_in = hit_bounds.contains_point(*position);
if 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();
return true;
}
@ -160,9 +155,6 @@ impl Element for MouseEventHandler {
state.prev_drag_position = None;
if !handled_in_child && state.clicked {
state.clicked = false;
if !state.hovered {
state.cursor_style_handle = None;
}
cx.notify();
if let Some(handler) = click_handler {
if hit_bounds.contains_point(*position) {

View File

@ -21,6 +21,7 @@ use anyhow::{anyhow, Result};
use async_task::Runnable;
pub use event::{Event, NavigationDirection};
use postage::oneshot;
use serde::Deserialize;
use std::{
any::Any,
path::{Path, PathBuf},
@ -125,11 +126,12 @@ pub enum PromptLevel {
Critical,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Deserialize)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
PointingHand,
IBeam,
}
#[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::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
};
let _: () = msg_send![cursor, set];
}

View File

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

View File

@ -7,6 +7,7 @@ use crate::{
fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
platform::CursorStyle,
ImageData,
};
@ -32,6 +33,7 @@ pub struct Layer {
image_glyphs: Vec<ImageGlyph>,
icons: Vec<Icon>,
paths: Vec<Path>,
cursor_styles: Vec<(RectF, CursorStyle)>,
}
#[derive(Default, Debug)]
@ -173,6 +175,13 @@ impl Scene {
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>) {
self.active_stacking_context_stack
.push(self.stacking_contexts.len());
@ -197,6 +206,10 @@ impl Scene {
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) {
self.active_layer().push_image(image)
}
@ -285,6 +298,7 @@ impl Layer {
glyphs: Default::default(),
icons: Default::default(),
paths: Default::default(),
cursor_styles: Default::default(),
}
}
@ -302,6 +316,14 @@ impl Layer {
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) {
if underline.width > 0. {
self.underlines.push(underline);

View File

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

View File

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

View File

@ -1983,7 +1983,14 @@ impl View for Workspace {
content.add_child(self.right_sidebar.render(&theme, cx));
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)
.boxed(),
)

View File

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

View File

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