mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Merge branch 'main' into jk
This commit is contained in:
commit
9d261cf859
@ -18,7 +18,7 @@ use project::Fs;
|
||||
use rich_text::RichText;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
||||
@ -304,8 +304,11 @@ impl ChatPanel {
|
||||
let last_message = active_chat.message(ix.saturating_sub(1));
|
||||
let this_message = active_chat.message(ix).clone();
|
||||
|
||||
let is_continuation_from_previous = last_message.id != this_message.id
|
||||
&& last_message.sender.id == this_message.sender.id;
|
||||
let duration_since_last_message = this_message.timestamp - last_message.timestamp;
|
||||
let is_continuation_from_previous = last_message.sender.id
|
||||
== this_message.sender.id
|
||||
&& last_message.id != this_message.id
|
||||
&& duration_since_last_message < Duration::from_secs(5 * 60);
|
||||
|
||||
if let ChannelMessageId::Saved(id) = this_message.id {
|
||||
if this_message
|
||||
@ -325,8 +328,6 @@ impl ChatPanel {
|
||||
Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
|
||||
});
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let belongs_to_user = Some(message.sender.id) == self.client.user_id();
|
||||
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
||||
(message.id, belongs_to_user || is_admin)
|
||||
@ -362,8 +363,8 @@ impl ChatPanel {
|
||||
)
|
||||
.child(
|
||||
Label::new(format_timestamp(
|
||||
OffsetDateTime::now_utc(),
|
||||
message.timestamp,
|
||||
now,
|
||||
self.local_timezone,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
@ -669,28 +670,44 @@ impl Panel for ChatPanel {
|
||||
impl EventEmitter<PanelEvent> for ChatPanel {}
|
||||
|
||||
fn format_timestamp(
|
||||
mut timestamp: OffsetDateTime,
|
||||
mut now: OffsetDateTime,
|
||||
local_timezone: UtcOffset,
|
||||
reference: OffsetDateTime,
|
||||
timestamp: OffsetDateTime,
|
||||
timezone: UtcOffset,
|
||||
) -> String {
|
||||
timestamp = timestamp.to_offset(local_timezone);
|
||||
now = now.to_offset(local_timezone);
|
||||
let timestamp_local = timestamp.to_offset(timezone);
|
||||
let timestamp_local_hour = timestamp_local.hour();
|
||||
|
||||
let today = now.date();
|
||||
let date = timestamp.date();
|
||||
let mut hour = timestamp.hour();
|
||||
let mut part = "am";
|
||||
if hour > 12 {
|
||||
hour -= 12;
|
||||
part = "pm";
|
||||
}
|
||||
if date == today {
|
||||
format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
|
||||
} else if date.next_day() == Some(today) {
|
||||
format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
|
||||
let hour_12 = match timestamp_local_hour {
|
||||
0 => 12, // Midnight
|
||||
13..=23 => timestamp_local_hour - 12, // PM hours
|
||||
_ => timestamp_local_hour, // AM hours
|
||||
};
|
||||
let meridiem = if timestamp_local_hour >= 12 {
|
||||
"pm"
|
||||
} else {
|
||||
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
|
||||
"am"
|
||||
};
|
||||
let timestamp_local_minute = timestamp_local.minute();
|
||||
let formatted_time = format!("{:02}:{:02} {}", hour_12, timestamp_local_minute, meridiem);
|
||||
|
||||
let reference_local = reference.to_offset(timezone);
|
||||
let reference_local_date = reference_local.date();
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
if timestamp_local_date == reference_local_date {
|
||||
return formatted_time;
|
||||
}
|
||||
|
||||
if reference_local_date.previous_day() == Some(timestamp_local_date) {
|
||||
return format!("yesterday at {}", formatted_time);
|
||||
}
|
||||
|
||||
format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.year()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -699,6 +716,7 @@ mod tests {
|
||||
use gpui::HighlightStyle;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rich_text::Highlight;
|
||||
use time::{Date, OffsetDateTime, Time, UtcOffset};
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
#[gpui::test]
|
||||
@ -747,4 +765,99 @@ mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_today() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"03:30 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 09:00 am"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_less_than_24_hours_ago() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 08:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_more_than_24_hours_ago() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 06:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_over_midnight() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 0, 5, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 11:55 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_over_month() {
|
||||
let reference = create_offset_datetime(1990, 4, 2, 9, 0, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 08:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_before_yesterday() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"04/10/1990"
|
||||
);
|
||||
}
|
||||
|
||||
fn test_timezone() -> UtcOffset {
|
||||
UtcOffset::from_hms(0, 0, 0).expect("Valid timezone offset")
|
||||
}
|
||||
|
||||
fn create_offset_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> OffsetDateTime {
|
||||
let date =
|
||||
Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day).unwrap();
|
||||
let time = Time::from_hms(hour, minute, second).unwrap();
|
||||
date.with_time(time).assume_utc() // Assume UTC for simplicity
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ impl fmt::Debug for Rgba {
|
||||
|
||||
impl Rgba {
|
||||
/// Create a new [`Rgba`] color by blending this and another color together
|
||||
/// TODO: find the source for this algorithm
|
||||
/// TODO!(docs): find the source for this algorithm
|
||||
pub fn blend(&self, other: Rgba) -> Self {
|
||||
if other.a >= 1.0 {
|
||||
other
|
||||
|
@ -8,13 +8,13 @@
|
||||
//! # Element Basics
|
||||
//!
|
||||
//! Elements are constructed by calling [`Render::render()`] on the root view of the window, which
|
||||
//! which recursively constructs the element tree from the current state of the application.
|
||||
//! which recursively constructs the element tree from the current state of the application,.
|
||||
//! These elements are then laid out by Taffy, and painted to the screen according to their own
|
||||
//! implementation of [`Element::paint()`]. Before the start of the next frame, the entire element
|
||||
//! tree and any callbacks they have registered with GPUI are dropped and the process repeats.
|
||||
//!
|
||||
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
|
||||
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], type.
|
||||
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
|
||||
//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id
|
||||
//! appears in the same place relative to other views and ElementIds in the frame, then the previous
|
||||
//! frame's state will be passed to the element's layout and paint methods.
|
||||
@ -30,7 +30,7 @@
|
||||
//!
|
||||
//! However, most of the time, you won't need to implement your own elements. GPUI provides a number of
|
||||
//! elements that should cover most common use cases out of the box and it's recommended that you use those
|
||||
//! to construct `components`, using the `RenderOnce` trait and the `#[derive(IntoElement)]` macro. Only implement
|
||||
//! to construct `components`, using the [`RenderOnce`] trait and the `#[derive(IntoElement)]` macro. Only implement
|
||||
//! elements when you need to take manual control of the layout and painting process, such as when using
|
||||
//! your own custom layout algorithm or rendering a code editor.
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
//! # Capturing and bubbling
|
||||
//!
|
||||
//! Note that while event dispatch in GPUI uses similar names and concepts to the web
|
||||
//! even API, the details are very different. See the documentation in [TODO
|
||||
//! even API, the details are very different. See the documentation in [TODO!(docs)
|
||||
//! DOCUMENT EVENT DISPATCH SOMEWHERE IN WINDOW CONTEXT] for more details
|
||||
//!
|
||||
|
||||
|
@ -9,11 +9,15 @@ use futures::FutureExt;
|
||||
use media::core_video::CVImageBuffer;
|
||||
use util::ResultExt;
|
||||
|
||||
/// A source of image content.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ImageSource {
|
||||
/// Image content will be loaded from provided URI at render time.
|
||||
Uri(SharedUrl),
|
||||
/// Cached image data
|
||||
Data(Arc<ImageData>),
|
||||
// TODO: move surface definitions into mac platform module
|
||||
/// A CoreVideo image buffer
|
||||
Surface(CVImageBuffer),
|
||||
}
|
||||
|
||||
@ -47,12 +51,14 @@ impl From<CVImageBuffer> for ImageSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// An image element.
|
||||
pub struct Img {
|
||||
interactivity: Interactivity,
|
||||
source: ImageSource,
|
||||
grayscale: bool,
|
||||
}
|
||||
|
||||
/// Create a new image element.
|
||||
pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||
Img {
|
||||
interactivity: Interactivity::default(),
|
||||
@ -62,6 +68,7 @@ pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||
}
|
||||
|
||||
impl Img {
|
||||
/// Set the image to be displayed in grayscale.
|
||||
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
||||
self.grayscale = grayscale;
|
||||
self
|
||||
|
@ -1,3 +1,11 @@
|
||||
//! A list element that can be used to render a large number of differently sized elements
|
||||
//! efficiently. Clients of this API need to ensure that elements outside of the scrolled
|
||||
//! area do not change their height for this element to function correctly. In order to minimize
|
||||
//! re-renders, this element's state is stored intrusively on your own views, so that your code
|
||||
//! can coordinate directly with the list element's cached state.
|
||||
//!
|
||||
//! If all of your elements are the same height, see [`UniformList`] for a simpler API
|
||||
|
||||
use crate::{
|
||||
point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||
DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
@ -8,6 +16,7 @@ use refineable::Refineable as _;
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
/// Construct a new list element
|
||||
pub fn list(state: ListState) -> List {
|
||||
List {
|
||||
state,
|
||||
@ -15,11 +24,13 @@ pub fn list(state: ListState) -> List {
|
||||
}
|
||||
}
|
||||
|
||||
/// A list element
|
||||
pub struct List {
|
||||
state: ListState,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
/// The list state that views must hold on behalf of the list element.
|
||||
#[derive(Clone)]
|
||||
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||
|
||||
@ -35,15 +46,24 @@ struct StateInner {
|
||||
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
|
||||
}
|
||||
|
||||
/// Whether the list is scrolling from top to bottom or bottom to top.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ListAlignment {
|
||||
/// The list is scrolling from top to bottom, like most lists.
|
||||
Top,
|
||||
/// The list is scrolling from bottom to top, like a chat log.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// A scroll event that has been converted to be in terms of the list's items.
|
||||
pub struct ListScrollEvent {
|
||||
/// The range of items currently visible in the list, after applying the scroll event.
|
||||
pub visible_range: Range<usize>,
|
||||
|
||||
/// The number of items that are currently visible in the list, after applying the scroll event.
|
||||
pub count: usize,
|
||||
|
||||
/// Whether the list has been scrolled.
|
||||
pub is_scrolled: bool,
|
||||
}
|
||||
|
||||
@ -74,6 +94,11 @@ struct UnrenderedCount(usize);
|
||||
struct Height(Pixels);
|
||||
|
||||
impl ListState {
|
||||
/// Construct a new list state, for storage on a view.
|
||||
///
|
||||
/// the overdraw parameter controls how much extra space is rendered
|
||||
/// above and below the visible area. This can help ensure that the list
|
||||
/// doesn't flicker or pop in when scrolling.
|
||||
pub fn new<F>(
|
||||
element_count: usize,
|
||||
orientation: ListAlignment,
|
||||
@ -111,10 +136,13 @@ impl ListState {
|
||||
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
}
|
||||
|
||||
/// The number of items in this list.
|
||||
pub fn item_count(&self) -> usize {
|
||||
self.0.borrow().items.summary().count
|
||||
}
|
||||
|
||||
/// Register with the list state that the items in `old_range` have been replaced
|
||||
/// by `count` new items that must be recalculated.
|
||||
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
@ -141,6 +169,7 @@ impl ListState {
|
||||
state.items = new_heights;
|
||||
}
|
||||
|
||||
/// Set a handler that will be called when the list is scrolled.
|
||||
pub fn set_scroll_handler(
|
||||
&self,
|
||||
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
||||
@ -148,10 +177,12 @@ impl ListState {
|
||||
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
||||
}
|
||||
|
||||
/// Get the current scroll offset, in terms of the list's items.
|
||||
pub fn logical_scroll_top(&self) -> ListOffset {
|
||||
self.0.borrow().logical_scroll_top()
|
||||
}
|
||||
|
||||
/// Scroll the list to the given offset
|
||||
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
let item_count = state.items.summary().count;
|
||||
@ -163,6 +194,7 @@ impl ListState {
|
||||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item, such that the item is fully visible.
|
||||
pub fn scroll_to_reveal_item(&self, ix: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
@ -193,7 +225,8 @@ impl ListState {
|
||||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
|
||||
/// Get the bounds for the given item in window coordinates.
|
||||
/// Get the bounds for the given item in window coordinates, if it's
|
||||
/// been rendered.
|
||||
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
|
||||
let state = &*self.0.borrow();
|
||||
|
||||
@ -310,9 +343,13 @@ impl std::fmt::Debug for ListItem {
|
||||
}
|
||||
}
|
||||
|
||||
/// An offset into the list's items, in terms of the item index and the number
|
||||
/// of pixels off the top left of the item.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ListOffset {
|
||||
/// The index of an item in the list
|
||||
pub item_ix: usize,
|
||||
/// The number of pixels to offset from the item index.
|
||||
pub offset_in_item: Pixels,
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,13 @@ use crate::{
|
||||
Point, Size, Style, WindowContext,
|
||||
};
|
||||
|
||||
/// The state that the overlay element uses to track its children.
|
||||
pub struct OverlayState {
|
||||
child_layout_ids: SmallVec<[LayoutId; 4]>,
|
||||
}
|
||||
|
||||
/// An overlay element that can be used to display UI that
|
||||
/// floats on top of other UI elements.
|
||||
pub struct Overlay {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
anchor_corner: AnchorCorner,
|
||||
@ -191,15 +194,21 @@ enum Axis {
|
||||
Vertical,
|
||||
}
|
||||
|
||||
/// Which algorithm to use when fitting the overlay to be inside the window.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum OverlayFitMode {
|
||||
/// Snap the overlay to the window edge
|
||||
SnapToWindow,
|
||||
/// Switch which corner anchor this overlay is attached to
|
||||
SwitchAnchor,
|
||||
}
|
||||
|
||||
/// Which algorithm to use when positioning the overlay.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum OverlayPositionMode {
|
||||
/// Position the overlay relative to the window
|
||||
Window,
|
||||
/// Position the overlay relative to its parent
|
||||
Local,
|
||||
}
|
||||
|
||||
@ -226,11 +235,16 @@ impl OverlayPositionMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Which corner of the overlay should be considered the anchor.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AnchorCorner {
|
||||
/// The top left corner
|
||||
TopLeft,
|
||||
/// The top right corner
|
||||
TopRight,
|
||||
/// The bottom left corner
|
||||
BottomLeft,
|
||||
/// The bottom right corner
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
@ -255,6 +269,7 @@ impl AnchorCorner {
|
||||
Bounds { origin, size }
|
||||
}
|
||||
|
||||
/// Get the point corresponding to this anchor corner in `bounds`.
|
||||
pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||
match self {
|
||||
Self::TopLeft => bounds.origin,
|
||||
|
@ -4,11 +4,13 @@ use crate::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
/// An SVG element.
|
||||
pub struct Svg {
|
||||
interactivity: Interactivity,
|
||||
path: Option<SharedString>,
|
||||
}
|
||||
|
||||
/// Create a new SVG element.
|
||||
pub fn svg() -> Svg {
|
||||
Svg {
|
||||
interactivity: Interactivity::default(),
|
||||
@ -17,6 +19,7 @@ pub fn svg() -> Svg {
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
/// Set the path to the SVG file for this element.
|
||||
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
|
@ -87,6 +87,7 @@ pub struct StyledText {
|
||||
}
|
||||
|
||||
impl StyledText {
|
||||
/// Construct a new styled text element from the given string.
|
||||
pub fn new(text: impl Into<SharedString>) -> Self {
|
||||
StyledText {
|
||||
text: text.into(),
|
||||
@ -94,6 +95,8 @@ impl StyledText {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the styling attributes for the given text, as well as
|
||||
/// as any ranges of text that have had their style customized.
|
||||
pub fn with_highlights(
|
||||
mut self,
|
||||
default_style: &TextStyle,
|
||||
@ -151,6 +154,7 @@ impl IntoElement for StyledText {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
|
||||
|
||||
@ -290,6 +294,7 @@ impl TextState {
|
||||
}
|
||||
}
|
||||
|
||||
/// A text element that can be interacted with.
|
||||
pub struct InteractiveText {
|
||||
element_id: ElementId,
|
||||
text: StyledText,
|
||||
@ -305,6 +310,7 @@ struct InteractiveTextClickEvent {
|
||||
mouse_up_index: usize,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct InteractiveTextState {
|
||||
text_state: TextState,
|
||||
mouse_down_index: Rc<Cell<Option<usize>>>,
|
||||
@ -314,6 +320,7 @@ pub struct InteractiveTextState {
|
||||
|
||||
/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
|
||||
impl InteractiveText {
|
||||
/// Creates a new InteractiveText from the given text.
|
||||
pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
|
||||
Self {
|
||||
element_id: id.into(),
|
||||
|
@ -1,3 +1,9 @@
|
||||
//! A scrollable list of elements with uniform height, optimized for large lists.
|
||||
//! Rather than use the full taffy layout system, uniform_list simply measures
|
||||
//! the first element and then lays out all remaining elements in a line based on that
|
||||
//! measurement. This is much faster than the full layout system, but only works for
|
||||
//! elements with uniform height.
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
|
||||
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
|
||||
@ -53,6 +59,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A list element for efficiently laying out and displaying a list of uniform-height elements.
|
||||
pub struct UniformList {
|
||||
id: ElementId,
|
||||
item_count: usize,
|
||||
@ -63,18 +70,22 @@ pub struct UniformList {
|
||||
scroll_handle: Option<UniformListScrollHandle>,
|
||||
}
|
||||
|
||||
/// A handle for controlling the scroll position of a uniform list.
|
||||
/// This should be stored in your view and passed to the uniform_list on each frame.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UniformListScrollHandle {
|
||||
deferred_scroll_to_item: Rc<RefCell<Option<usize>>>,
|
||||
}
|
||||
|
||||
impl UniformListScrollHandle {
|
||||
/// Create a new scroll handle to bind to a uniform list.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
deferred_scroll_to_item: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item index.
|
||||
pub fn scroll_to_item(&mut self, ix: usize) {
|
||||
self.deferred_scroll_to_item.replace(Some(ix));
|
||||
}
|
||||
@ -86,6 +97,7 @@ impl Styled for UniformList {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Default)]
|
||||
pub struct UniformListState {
|
||||
interactive: InteractiveElementState,
|
||||
@ -262,6 +274,7 @@ impl IntoElement for UniformList {
|
||||
}
|
||||
|
||||
impl UniformList {
|
||||
/// Selects a specific list item for measurement.
|
||||
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
||||
self.item_to_measure_index = item_index.unwrap_or(0);
|
||||
self
|
||||
@ -284,6 +297,7 @@ impl UniformList {
|
||||
item_to_measure.measure(available_space, cx)
|
||||
}
|
||||
|
||||
/// Track and render scroll state of this list with reference to the given scroll handle.
|
||||
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
|
||||
self.scroll_handle = Some(handle);
|
||||
self
|
||||
|
@ -21,11 +21,15 @@ use waker_fn::waker_fn;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
/// A pointer to the executor that is currently running,
|
||||
/// for spawning background tasks.
|
||||
#[derive(Clone)]
|
||||
pub struct BackgroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
}
|
||||
|
||||
/// A pointer to the executor that is currently running,
|
||||
/// for spawning tasks on the main thread.
|
||||
#[derive(Clone)]
|
||||
pub struct ForegroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
@ -37,11 +41,14 @@ pub struct ForegroundExecutor {
|
||||
/// It implements [`Future`] so you can `.await` on it.
|
||||
///
|
||||
/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
|
||||
/// the task to continue running in the background, but with no way to return a value.
|
||||
/// the task to continue running, but with no way to return a value.
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub enum Task<T> {
|
||||
/// A task that is ready to return a value
|
||||
Ready(Option<T>),
|
||||
|
||||
/// A task that is currently running.
|
||||
Spawned(async_task::Task<T>),
|
||||
}
|
||||
|
||||
@ -87,6 +94,8 @@ impl<T> Future for Task<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A task label is an opaque identifier that you can use to
|
||||
/// refer to a task in tests.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TaskLabel(NonZeroUsize);
|
||||
|
||||
@ -97,6 +106,7 @@ impl Default for TaskLabel {
|
||||
}
|
||||
|
||||
impl TaskLabel {
|
||||
/// Construct a new task label.
|
||||
pub fn new() -> Self {
|
||||
static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
|
||||
Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
|
||||
@ -363,6 +373,7 @@ impl BackgroundExecutor {
|
||||
|
||||
/// ForegroundExecutor runs things on the main thread.
|
||||
impl ForegroundExecutor {
|
||||
/// Creates a new ForegroundExecutor from the given PlatformDispatcher.
|
||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||
Self {
|
||||
dispatcher,
|
||||
@ -411,13 +422,14 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a future into this scope.
|
||||
pub fn spawn<F>(&mut self, f: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'a,
|
||||
{
|
||||
let tx = self.tx.clone().unwrap();
|
||||
|
||||
// Safety: The 'a lifetime is guaranteed to outlive any of these futures because
|
||||
// SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
|
||||
// dropping this `Scope` blocks until all of the futures have resolved.
|
||||
let f = unsafe {
|
||||
mem::transmute::<
|
||||
|
@ -1,3 +1,31 @@
|
||||
//! # Welcome to GPUI!
|
||||
//!
|
||||
//! GPUI is a hybrid immediate and retained mode, GPU accelerated, UI framework
|
||||
//! for Rust, designed to support a wide variety of applications. GPUI is currently
|
||||
//! being actively developed and improved for the [Zed code editor](https://zed.dev/), and new versions
|
||||
//! will have breaking changes. You'll probably need to use the latest stable version
|
||||
//! of rust to use GPUI.
|
||||
//!
|
||||
//! # Getting started with GPUI
|
||||
//!
|
||||
//! TODO!(docs): Write a code sample showing how to create a window and render a simple
|
||||
//! div
|
||||
//!
|
||||
//! # Drawing interesting things
|
||||
//!
|
||||
//! TODO!(docs): Expand demo to show how to draw a more interesting scene, with
|
||||
//! a counter to store state and a button to increment it.
|
||||
//!
|
||||
//! # Interacting with your application state
|
||||
//!
|
||||
//! TODO!(docs): Expand demo to show GPUI entity interactions, like subscriptions and entities
|
||||
//! maybe make a network request to show async stuff?
|
||||
//!
|
||||
//! # Conclusion
|
||||
//!
|
||||
//! TODO!(docs): Wrap up with a conclusion and links to other places? Zed / GPUI website?
|
||||
//! Discord for chatting about it? Other tutorials or references?
|
||||
|
||||
#[macro_use]
|
||||
mod action;
|
||||
mod app;
|
||||
@ -58,10 +86,10 @@ pub use elements::*;
|
||||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use gpui_macros::{register_action, test, IntoElement, Render};
|
||||
pub use image_cache::*;
|
||||
use image_cache::*;
|
||||
pub use input::*;
|
||||
pub use interactive::*;
|
||||
pub use key_dispatch::*;
|
||||
use key_dispatch::*;
|
||||
pub use keymap::*;
|
||||
pub use platform::*;
|
||||
pub use refineable::*;
|
||||
@ -73,7 +101,7 @@ pub use smol::Timer;
|
||||
pub use style::*;
|
||||
pub use styled::*;
|
||||
pub use subscription::*;
|
||||
pub use svg_renderer::*;
|
||||
use svg_renderer::*;
|
||||
pub use taffy::{AvailableSpace, LayoutId};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use test::*;
|
||||
@ -82,20 +110,23 @@ pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::BorrowMut,
|
||||
};
|
||||
use std::{any::Any, borrow::BorrowMut};
|
||||
use taffy::TaffyLayoutEngine;
|
||||
|
||||
/// The context trait, allows the different contexts in GPUI to be used
|
||||
/// interchangeably for certain operations.
|
||||
pub trait Context {
|
||||
/// The result type for this context, used for async contexts that
|
||||
/// can't hold a direct reference to the application context.
|
||||
type Result<T>;
|
||||
|
||||
/// Create a new model in the app context.
|
||||
fn new_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>;
|
||||
|
||||
/// Update a model in the app context.
|
||||
fn update_model<T, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
@ -104,6 +135,7 @@ pub trait Context {
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
/// Read a model from the app context.
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
@ -112,10 +144,12 @@ pub trait Context {
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
/// Update a window for the given handle.
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
|
||||
|
||||
/// Read a window off of the application context.
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
@ -125,7 +159,10 @@ pub trait Context {
|
||||
T: 'static;
|
||||
}
|
||||
|
||||
/// This trait is used for the different visual contexts in GPUI that
|
||||
/// require a window to be present.
|
||||
pub trait VisualContext: Context {
|
||||
/// Construct a new view in the window referenced by this context.
|
||||
fn new_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
@ -133,12 +170,14 @@ pub trait VisualContext: Context {
|
||||
where
|
||||
V: 'static + Render;
|
||||
|
||||
/// Update a view with the given callback
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R>;
|
||||
|
||||
/// Replace the root view of a window with a new view.
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
@ -146,38 +185,47 @@ pub trait VisualContext: Context {
|
||||
where
|
||||
V: 'static + Render;
|
||||
|
||||
/// Focus a view in the window, if it implements the [`FocusableView`] trait.
|
||||
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: FocusableView;
|
||||
|
||||
/// Dismiss a view in the window, if it implements the [`ManagedView`] trait.
|
||||
fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: ManagedView;
|
||||
}
|
||||
|
||||
/// A trait that allows models and views to be interchangeable in certain operations
|
||||
pub trait Entity<T>: Sealed {
|
||||
/// The weak reference type for this entity.
|
||||
type Weak: 'static;
|
||||
|
||||
/// The ID for this entity
|
||||
fn entity_id(&self) -> EntityId;
|
||||
|
||||
/// Downgrade this entity to a weak reference.
|
||||
fn downgrade(&self) -> Self::Weak;
|
||||
|
||||
/// Upgrade this entity from a weak reference.
|
||||
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// A trait for tying together the types of a GPUI entity and the events it can
|
||||
/// emit.
|
||||
pub trait EventEmitter<E: Any>: 'static {}
|
||||
|
||||
pub enum GlobalKey {
|
||||
Numeric(usize),
|
||||
View(EntityId),
|
||||
Type(TypeId),
|
||||
}
|
||||
|
||||
/// A helper trait for auto-implementing certain methods on contexts that
|
||||
/// can be used interchangeably.
|
||||
pub trait BorrowAppContext {
|
||||
/// Run a closure with a text style pushed onto the context.
|
||||
fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
|
||||
/// Set a global value on the context.
|
||||
fn set_global<T: 'static>(&mut self, global: T);
|
||||
}
|
||||
|
||||
@ -204,7 +252,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A flatten equivalent for anyhow `Result`s.
|
||||
pub trait Flatten<T> {
|
||||
/// Convert this type into a simple `Result<T>`.
|
||||
fn flatten(self) -> Result<T>;
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,12 @@ use thiserror::Error;
|
||||
use util::http::{self, HttpClient};
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub struct RenderImageParams {
|
||||
pub(crate) struct RenderImageParams {
|
||||
pub(crate) image_id: ImageId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum Error {
|
||||
pub(crate) enum Error {
|
||||
#[error("http error: {0}")]
|
||||
Client(#[from] http::Error),
|
||||
#[error("IO error: {0}")]
|
||||
@ -42,7 +42,7 @@ impl From<ImageError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageCache {
|
||||
pub(crate) struct ImageCache {
|
||||
client: Arc<dyn HttpClient>,
|
||||
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
|
||||
}
|
||||
|
@ -3,20 +3,33 @@ use std::ops::Range;
|
||||
|
||||
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
|
||||
///
|
||||
/// Once your view `V` implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
|
||||
/// Once your view implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
|
||||
/// This input handler can then be assigned during paint by calling [`WindowContext::handle_input`].
|
||||
///
|
||||
/// See [`InputHandler`] for details on how to implement each method.
|
||||
pub trait ViewInputHandler: 'static + Sized {
|
||||
/// See [`InputHandler::text_for_range`] for details
|
||||
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
|
||||
-> Option<String>;
|
||||
|
||||
/// See [`InputHandler::selected_text_range`] for details
|
||||
fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
|
||||
/// See [`InputHandler::marked_text_range`] for details
|
||||
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
|
||||
/// See [`InputHandler::unmark_text`] for details
|
||||
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
|
||||
|
||||
/// See [`InputHandler::replace_text_in_range`] for details
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
|
||||
/// See [`InputHandler::replace_and_mark_text_in_range`] for details
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
@ -24,6 +37,8 @@ pub trait ViewInputHandler: 'static + Sized {
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
|
||||
/// See [`InputHandler::bounds_for_range`] for details
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
|
@ -4,15 +4,25 @@ use crate::{
|
||||
use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
|
||||
|
||||
/// An event from a platform input source.
|
||||
pub trait InputEvent: Sealed + 'static {
|
||||
/// Convert this event into the platform input enum.
|
||||
fn to_platform_input(self) -> PlatformInput;
|
||||
}
|
||||
|
||||
/// A key event from the platform.
|
||||
pub trait KeyEvent: InputEvent {}
|
||||
|
||||
/// A mouse event from the platform.
|
||||
pub trait MouseEvent: InputEvent {}
|
||||
|
||||
/// The key down event equivalent for the platform.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct KeyDownEvent {
|
||||
/// The keystroke that was generated.
|
||||
pub keystroke: Keystroke,
|
||||
|
||||
/// Whether the key is currently held down.
|
||||
pub is_held: bool,
|
||||
}
|
||||
|
||||
@ -24,8 +34,10 @@ impl InputEvent for KeyDownEvent {
|
||||
}
|
||||
impl KeyEvent for KeyDownEvent {}
|
||||
|
||||
/// The key up event equivalent for the platform.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyUpEvent {
|
||||
/// The keystroke that was released.
|
||||
pub keystroke: Keystroke,
|
||||
}
|
||||
|
||||
@ -37,8 +49,10 @@ impl InputEvent for KeyUpEvent {
|
||||
}
|
||||
impl KeyEvent for KeyUpEvent {}
|
||||
|
||||
/// The modifiers changed event equivalent for the platform.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ModifiersChangedEvent {
|
||||
/// The new state of the modifier keys
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
@ -62,17 +76,28 @@ impl Deref for ModifiersChangedEvent {
|
||||
/// Based on the winit enum of the same name.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum TouchPhase {
|
||||
/// The touch started.
|
||||
Started,
|
||||
/// The touch event is moving.
|
||||
#[default]
|
||||
Moved,
|
||||
/// The touch phase has ended
|
||||
Ended,
|
||||
}
|
||||
|
||||
/// A mouse down event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseDownEvent {
|
||||
/// Which mouse button was pressed.
|
||||
pub button: MouseButton,
|
||||
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The modifiers that were held down when the mouse was pressed.
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// The number of times the button has been clicked.
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
@ -84,11 +109,19 @@ impl InputEvent for MouseDownEvent {
|
||||
}
|
||||
impl MouseEvent for MouseDownEvent {}
|
||||
|
||||
/// A mouse up event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUpEvent {
|
||||
/// Which mouse button was released.
|
||||
pub button: MouseButton,
|
||||
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The modifiers that were held down when the mouse was released.
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// The number of times the button has been clicked.
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
@ -100,21 +133,34 @@ impl InputEvent for MouseUpEvent {
|
||||
}
|
||||
impl MouseEvent for MouseUpEvent {}
|
||||
|
||||
/// A click event, generated when a mouse button is pressed and released.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ClickEvent {
|
||||
/// The mouse event when the button was pressed.
|
||||
pub down: MouseDownEvent,
|
||||
|
||||
/// The mouse event when the button was released.
|
||||
pub up: MouseUpEvent,
|
||||
}
|
||||
|
||||
/// An enum representing the mouse button that was pressed.
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum MouseButton {
|
||||
/// The left mouse button.
|
||||
Left,
|
||||
|
||||
/// The right mouse button.
|
||||
Right,
|
||||
|
||||
/// The middle mouse button.
|
||||
Middle,
|
||||
|
||||
/// A navigation button, such as back or forward.
|
||||
Navigate(NavigationDirection),
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
/// Get all the mouse buttons in a list.
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
MouseButton::Left,
|
||||
@ -132,9 +178,13 @@ impl Default for MouseButton {
|
||||
}
|
||||
}
|
||||
|
||||
/// A navigation direction, such as back or forward.
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum NavigationDirection {
|
||||
/// The back button.
|
||||
Back,
|
||||
|
||||
/// The forward button.
|
||||
Forward,
|
||||
}
|
||||
|
||||
@ -144,10 +194,16 @@ impl Default for NavigationDirection {
|
||||
}
|
||||
}
|
||||
|
||||
/// A mouse move event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseMoveEvent {
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The mouse button that was pressed, if any.
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
|
||||
/// The modifiers that were held down when the mouse was moved.
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
@ -160,16 +216,25 @@ impl InputEvent for MouseMoveEvent {
|
||||
impl MouseEvent for MouseMoveEvent {}
|
||||
|
||||
impl MouseMoveEvent {
|
||||
/// Returns true if the left mouse button is currently held down.
|
||||
pub fn dragging(&self) -> bool {
|
||||
self.pressed_button == Some(MouseButton::Left)
|
||||
}
|
||||
}
|
||||
|
||||
/// A mouse wheel event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScrollWheelEvent {
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The change in scroll wheel position for this event.
|
||||
pub delta: ScrollDelta,
|
||||
|
||||
/// The modifiers that were held down when the mouse was moved.
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// The phase of the touch event.
|
||||
pub touch_phase: TouchPhase,
|
||||
}
|
||||
|
||||
@ -189,9 +254,12 @@ impl Deref for ScrollWheelEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// The scroll delta for a scroll wheel event.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ScrollDelta {
|
||||
/// An exact scroll delta in pixels.
|
||||
Pixels(Point<Pixels>),
|
||||
/// An inexact scroll delta in lines.
|
||||
Lines(Point<f32>),
|
||||
}
|
||||
|
||||
@ -202,6 +270,7 @@ impl Default for ScrollDelta {
|
||||
}
|
||||
|
||||
impl ScrollDelta {
|
||||
/// Returns true if this is a precise scroll delta in pixels.
|
||||
pub fn precise(&self) -> bool {
|
||||
match self {
|
||||
ScrollDelta::Pixels(_) => true,
|
||||
@ -209,6 +278,7 @@ impl ScrollDelta {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this scroll event into exact pixels.
|
||||
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
|
||||
match self {
|
||||
ScrollDelta::Pixels(delta) => *delta,
|
||||
@ -216,6 +286,7 @@ impl ScrollDelta {
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines two scroll deltas into one.
|
||||
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
|
||||
match (self, other) {
|
||||
(ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
|
||||
@ -231,10 +302,15 @@ impl ScrollDelta {
|
||||
}
|
||||
}
|
||||
|
||||
/// A mouse exit event from the platform, generated when the mouse leaves the window.
|
||||
/// The position generated should be just outside of the window's bounds.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseExitEvent {
|
||||
/// The position of the mouse relative to the window.
|
||||
pub position: Point<Pixels>,
|
||||
/// The mouse button that was pressed, if any.
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
/// The modifiers that were held down when the mouse was moved.
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
@ -254,10 +330,12 @@ impl Deref for MouseExitEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of paths from the platform, such as from a file drop.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
||||
|
||||
impl ExternalPaths {
|
||||
/// Convert this collection of paths into a slice.
|
||||
pub fn paths(&self) -> &[PathBuf] {
|
||||
&self.0
|
||||
}
|
||||
@ -269,18 +347,27 @@ impl Render for ExternalPaths {
|
||||
}
|
||||
}
|
||||
|
||||
/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileDropEvent {
|
||||
/// The files have entered the window.
|
||||
Entered {
|
||||
/// The position of the mouse relative to the window.
|
||||
position: Point<Pixels>,
|
||||
/// The paths of the files that are being dragged.
|
||||
paths: ExternalPaths,
|
||||
},
|
||||
/// The files are being dragged over the window
|
||||
Pending {
|
||||
/// The position of the mouse relative to the window.
|
||||
position: Point<Pixels>,
|
||||
},
|
||||
/// The files have been dropped onto the window.
|
||||
Submit {
|
||||
/// The position of the mouse relative to the window.
|
||||
position: Point<Pixels>,
|
||||
},
|
||||
/// The user has stopped dragging the files over the window.
|
||||
Exited,
|
||||
}
|
||||
|
||||
@ -292,40 +379,31 @@ impl InputEvent for FileDropEvent {
|
||||
}
|
||||
impl MouseEvent for FileDropEvent {}
|
||||
|
||||
/// An enum corresponding to all kinds of platform input events.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PlatformInput {
|
||||
/// A key was pressed.
|
||||
KeyDown(KeyDownEvent),
|
||||
/// A key was released.
|
||||
KeyUp(KeyUpEvent),
|
||||
/// The keyboard modifiers were changed.
|
||||
ModifiersChanged(ModifiersChangedEvent),
|
||||
/// The mouse was pressed.
|
||||
MouseDown(MouseDownEvent),
|
||||
/// The mouse was released.
|
||||
MouseUp(MouseUpEvent),
|
||||
/// The mouse was moved.
|
||||
MouseMove(MouseMoveEvent),
|
||||
/// The mouse exited the window.
|
||||
MouseExited(MouseExitEvent),
|
||||
/// The scroll wheel was used.
|
||||
ScrollWheel(ScrollWheelEvent),
|
||||
/// Files were dragged and dropped onto the window.
|
||||
FileDrop(FileDropEvent),
|
||||
}
|
||||
|
||||
impl PlatformInput {
|
||||
pub fn position(&self) -> Option<Point<Pixels>> {
|
||||
match self {
|
||||
PlatformInput::KeyDown { .. } => None,
|
||||
PlatformInput::KeyUp { .. } => None,
|
||||
PlatformInput::ModifiersChanged { .. } => None,
|
||||
PlatformInput::MouseDown(event) => Some(event.position),
|
||||
PlatformInput::MouseUp(event) => Some(event.position),
|
||||
PlatformInput::MouseMove(event) => Some(event.position),
|
||||
PlatformInput::MouseExited(event) => Some(event.position),
|
||||
PlatformInput::ScrollWheel(event) => Some(event.position),
|
||||
PlatformInput::FileDrop(FileDropEvent::Exited) => None,
|
||||
PlatformInput::FileDrop(
|
||||
FileDropEvent::Entered { position, .. }
|
||||
| FileDropEvent::Pending { position, .. }
|
||||
| FileDropEvent::Submit { position, .. },
|
||||
) => Some(*position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_event(&self) -> Option<&dyn Any> {
|
||||
pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
|
||||
match self {
|
||||
PlatformInput::KeyDown { .. } => None,
|
||||
PlatformInput::KeyUp { .. } => None,
|
||||
@ -339,7 +417,7 @@ impl PlatformInput {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keyboard_event(&self) -> Option<&dyn Any> {
|
||||
pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
|
||||
match self {
|
||||
PlatformInput::KeyDown(event) => Some(event),
|
||||
PlatformInput::KeyUp(event) => Some(event),
|
||||
|
@ -13,7 +13,7 @@ use std::{
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct DispatchNodeId(usize);
|
||||
pub(crate) struct DispatchNodeId(usize);
|
||||
|
||||
pub(crate) struct DispatchTree {
|
||||
node_stack: Vec<DispatchNodeId>,
|
||||
|
@ -2,6 +2,7 @@ use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
|
||||
use anyhow::Result;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A keybinding and it's associated metadata, from the keymap.
|
||||
pub struct KeyBinding {
|
||||
pub(crate) action: Box<dyn Action>,
|
||||
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
@ -19,10 +20,12 @@ impl Clone for KeyBinding {
|
||||
}
|
||||
|
||||
impl KeyBinding {
|
||||
/// Construct a new keybinding from the given data.
|
||||
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
|
||||
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
|
||||
}
|
||||
|
||||
/// Load a keybinding from the given raw data.
|
||||
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||
let context = if let Some(context) = context {
|
||||
Some(KeyBindingContextPredicate::parse(context)?)
|
||||
@ -42,6 +45,7 @@ impl KeyBinding {
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the given keystrokes match this binding.
|
||||
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
|
||||
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
|
||||
// If the binding is completed, push it onto the matches list
|
||||
@ -55,10 +59,12 @@ impl KeyBinding {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the keystrokes associated with this binding
|
||||
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||
self.keystrokes.as_slice()
|
||||
}
|
||||
|
||||
/// Get the action associated with this binding
|
||||
pub fn action(&self) -> &dyn Action {
|
||||
self.action.as_ref()
|
||||
}
|
||||
|
@ -3,6 +3,10 @@ use anyhow::{anyhow, Result};
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt;
|
||||
|
||||
/// A datastructure for resolving whether an action should be dispatched
|
||||
/// at this point in the element tree. Contains a set of identifiers
|
||||
/// and/or key value pairs representing the current context for the
|
||||
/// keymap.
|
||||
#[derive(Clone, Default, Eq, PartialEq, Hash)]
|
||||
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
||||
|
||||
@ -21,6 +25,11 @@ impl<'a> TryFrom<&'a str> for KeyContext {
|
||||
}
|
||||
|
||||
impl KeyContext {
|
||||
/// Parse a key context from a string.
|
||||
/// The key context format is very simple:
|
||||
/// - either a single identifier, such as `StatusBar`
|
||||
/// - or a key value pair, such as `mode = visible`
|
||||
/// - separated by whitespace, such as `StatusBar mode = visible`
|
||||
pub fn parse(source: &str) -> Result<Self> {
|
||||
let mut context = Self::default();
|
||||
let source = skip_whitespace(source);
|
||||
@ -53,14 +62,17 @@ impl KeyContext {
|
||||
Self::parse_expr(source, context)
|
||||
}
|
||||
|
||||
/// Check if this context is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Clear this context.
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
|
||||
/// Extend this context with another context.
|
||||
pub fn extend(&mut self, other: &Self) {
|
||||
for entry in &other.0 {
|
||||
if !self.contains(&entry.key) {
|
||||
@ -69,6 +81,7 @@ impl KeyContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an identifier to this context, if it's not already in this context.
|
||||
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
|
||||
let key = identifier.into();
|
||||
|
||||
@ -77,6 +90,7 @@ impl KeyContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a key value pair in this context, if it's not already set.
|
||||
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
|
||||
let key = key.into();
|
||||
if !self.contains(&key) {
|
||||
@ -87,10 +101,12 @@ impl KeyContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this context contains a given identifier or key.
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
self.0.iter().any(|entry| entry.key.as_ref() == key)
|
||||
}
|
||||
|
||||
/// Get the associated value for a given identifier or key.
|
||||
pub fn get(&self, key: &str) -> Option<&SharedString> {
|
||||
self.0
|
||||
.iter()
|
||||
@ -117,20 +133,31 @@ impl fmt::Debug for KeyContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// A datastructure for resolving whether an action should be dispatched
|
||||
/// Representing a small language for describing which contexts correspond
|
||||
/// to which actions.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum KeyBindingContextPredicate {
|
||||
/// A predicate that will match a given identifier.
|
||||
Identifier(SharedString),
|
||||
/// A predicate that will match a given key-value pair.
|
||||
Equal(SharedString, SharedString),
|
||||
/// A predicate that will match a given key-value pair not being present.
|
||||
NotEqual(SharedString, SharedString),
|
||||
/// A predicate that will match a given predicate appearing below another predicate.
|
||||
/// in the element tree
|
||||
Child(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
),
|
||||
/// Predicate that will invert another predicate.
|
||||
Not(Box<KeyBindingContextPredicate>),
|
||||
/// A predicate that will match if both of its children match.
|
||||
And(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
),
|
||||
/// A predicate that will match if either of its children match.
|
||||
Or(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
@ -138,6 +165,34 @@ pub enum KeyBindingContextPredicate {
|
||||
}
|
||||
|
||||
impl KeyBindingContextPredicate {
|
||||
/// Parse a string in the same format as the keymap's context field.
|
||||
///
|
||||
/// A basic equivalence check against a set of identifiers can performed by
|
||||
/// simply writing a string:
|
||||
///
|
||||
/// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
|
||||
///
|
||||
/// You can also specify a key-value pair:
|
||||
///
|
||||
/// `mode == visible` -> A predicate that will match a context with the key `mode`
|
||||
/// with the value `visible`
|
||||
///
|
||||
/// And a logical operations combining these two checks:
|
||||
///
|
||||
/// `StatusBar && mode == visible` -> A predicate that will match a context with the
|
||||
/// identifier `StatusBar` and the key `mode`
|
||||
/// with the value `visible`
|
||||
///
|
||||
///
|
||||
/// There is also a special child `>` operator that will match a predicate that is
|
||||
/// below another predicate:
|
||||
///
|
||||
/// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
|
||||
/// and a child context that has the key `mode` with the
|
||||
/// value `visible`
|
||||
///
|
||||
/// This syntax supports `!=`, `||` and `&&` as logical operators.
|
||||
/// You can also preface an operation or check with a `!` to negate it.
|
||||
pub fn parse(source: &str) -> Result<Self> {
|
||||
let source = skip_whitespace(source);
|
||||
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
||||
@ -148,6 +203,7 @@ impl KeyBindingContextPredicate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Eval a predicate against a set of contexts, arranged from lowest to highest.
|
||||
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
|
||||
let Some(context) = contexts.last() else {
|
||||
return false;
|
||||
|
@ -6,9 +6,12 @@ use std::{
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
/// An opaque identifier of which version of the keymap is currently active.
|
||||
/// The keymap's version is changed whenever bindings are added or removed.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub struct KeymapVersion(usize);
|
||||
|
||||
/// A collection of key bindings for the user's application.
|
||||
#[derive(Default)]
|
||||
pub struct Keymap {
|
||||
bindings: Vec<KeyBinding>,
|
||||
@ -19,16 +22,19 @@ pub struct Keymap {
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
/// Create a new keymap with the given bindings.
|
||||
pub fn new(bindings: Vec<KeyBinding>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.add_bindings(bindings);
|
||||
this
|
||||
}
|
||||
|
||||
/// Get the current version of the keymap.
|
||||
pub fn version(&self) -> KeymapVersion {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Add more bindings to the keymap.
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
let no_action_id = (NoAction {}).type_id();
|
||||
|
||||
@ -51,6 +57,7 @@ impl Keymap {
|
||||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
/// Reset this keymap to its initial state.
|
||||
pub fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
@ -77,6 +84,7 @@ impl Keymap {
|
||||
.filter(move |binding| binding.action().partial_eq(action))
|
||||
}
|
||||
|
||||
/// Check if the given binding is enabled, given a certain key context.
|
||||
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
|
||||
// If binding has a context predicate, it must match the current context,
|
||||
if let Some(predicate) = &binding.context_predicate {
|
||||
|
@ -3,7 +3,7 @@ use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeystrokeMatcher {
|
||||
pub(crate) struct KeystrokeMatcher {
|
||||
pending_keystrokes: Vec<Keystroke>,
|
||||
keymap: Arc<Mutex<Keymap>>,
|
||||
keymap_version: KeymapVersion,
|
||||
@ -41,7 +41,7 @@ impl KeystrokeMatcher {
|
||||
/// - KeyMatch::Complete(matches) =>
|
||||
/// One or more bindings have received the necessary key presses.
|
||||
/// Bindings added later will take precedence over earlier bindings.
|
||||
pub fn match_keystroke(
|
||||
pub(crate) fn match_keystroke(
|
||||
&mut self,
|
||||
keystroke: &Keystroke,
|
||||
context_stack: &[KeyContext],
|
||||
@ -88,6 +88,10 @@ impl KeystrokeMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of matching a keystroke against a given keybinding.
|
||||
/// - KeyMatch::None => No match is valid for this key given any pending keystrokes.
|
||||
/// - KeyMatch::Pending => There exist bindings that is still waiting for more keys.
|
||||
/// - KeyMatch::Some(matches) => One or more bindings have received the necessary key presses.
|
||||
#[derive(Debug)]
|
||||
pub enum KeyMatch {
|
||||
None,
|
||||
@ -95,341 +99,3 @@ pub enum KeyMatch {
|
||||
Matched,
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
|
||||
// use serde_derive::Deserialize;
|
||||
|
||||
// use super::*;
|
||||
// use crate::{self as gpui, KeyBindingContextPredicate, Modifiers};
|
||||
// use crate::{actions, KeyBinding};
|
||||
|
||||
// #[test]
|
||||
// fn test_keymap_and_view_ordering() {
|
||||
// actions!(test, [EditorAction, ProjectPanelAction]);
|
||||
|
||||
// let mut editor = KeyContext::default();
|
||||
// editor.add("Editor");
|
||||
|
||||
// let mut project_panel = KeyContext::default();
|
||||
// project_panel.add("ProjectPanel");
|
||||
|
||||
// // Editor 'deeper' in than project panel
|
||||
// let dispatch_path = vec![project_panel, editor];
|
||||
|
||||
// // But editor actions 'higher' up in keymap
|
||||
// let keymap = Keymap::new(vec![
|
||||
// KeyBinding::new("left", EditorAction, Some("Editor")),
|
||||
// KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")),
|
||||
// ]);
|
||||
|
||||
// let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
|
||||
|
||||
// let matches = matcher
|
||||
// .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path)
|
||||
// .matches()
|
||||
// .unwrap();
|
||||
|
||||
// assert!(matches[0].partial_eq(&EditorAction));
|
||||
// assert!(matches.get(1).is_none());
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_multi_keystroke_match() {
|
||||
// actions!(test, [B, AB, C, D, DA, E, EF]);
|
||||
|
||||
// let mut context1 = KeyContext::default();
|
||||
// context1.add("1");
|
||||
|
||||
// let mut context2 = KeyContext::default();
|
||||
// context2.add("2");
|
||||
|
||||
// let dispatch_path = vec![context2, context1];
|
||||
|
||||
// let keymap = Keymap::new(vec![
|
||||
// KeyBinding::new("a b", AB, Some("1")),
|
||||
// KeyBinding::new("b", B, Some("2")),
|
||||
// KeyBinding::new("c", C, Some("2")),
|
||||
// KeyBinding::new("d", D, Some("1")),
|
||||
// KeyBinding::new("d", D, Some("2")),
|
||||
// KeyBinding::new("d a", DA, Some("2")),
|
||||
// ]);
|
||||
|
||||
// let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
|
||||
|
||||
// // Binding with pending prefix always takes precedence
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Pending,
|
||||
// );
|
||||
// // B alone doesn't match because a was pending, so AB is returned instead
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Some(vec![Box::new(AB)]),
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // Without an a prefix, B is dispatched like expected
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]),
|
||||
// KeyMatch::Some(vec![Box::new(B)]),
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // If a is prefixed, C will not be dispatched because there
|
||||
// // was a pending binding for it
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Pending,
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path),
|
||||
// KeyMatch::None,
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // If a single keystroke matches multiple bindings in the tree
|
||||
// // only one of them is returned.
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Some(vec![Box::new(D)]),
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_keystroke_parsing() {
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("ctrl-p").unwrap(),
|
||||
// Keystroke {
|
||||
// key: "p".into(),
|
||||
// modifiers: Modifiers {
|
||||
// control: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// command: false,
|
||||
// function: false,
|
||||
// },
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("alt-shift-down").unwrap(),
|
||||
// Keystroke {
|
||||
// key: "down".into(),
|
||||
// modifiers: Modifiers {
|
||||
// control: false,
|
||||
// alt: true,
|
||||
// shift: true,
|
||||
// command: false,
|
||||
// function: false,
|
||||
// },
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("shift-cmd--").unwrap(),
|
||||
// Keystroke {
|
||||
// key: "-".into(),
|
||||
// modifiers: Modifiers {
|
||||
// control: false,
|
||||
// alt: false,
|
||||
// shift: true,
|
||||
// command: true,
|
||||
// function: false,
|
||||
// },
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_predicate_parsing() {
|
||||
// use KeyBindingContextPredicate::*;
|
||||
|
||||
// assert_eq!(
|
||||
// KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
|
||||
// And(
|
||||
// Box::new(Identifier("a".into())),
|
||||
// Box::new(Or(
|
||||
// Box::new(Equal("b".into(), "c".into())),
|
||||
// Box::new(NotEqual("d".into(), "e".into())),
|
||||
// ))
|
||||
// )
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// KeyBindingContextPredicate::parse("!a").unwrap(),
|
||||
// Not(Box::new(Identifier("a".into())),)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_predicate_eval() {
|
||||
// let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap();
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// assert!(!predicate.eval(&[context]));
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// context.add("b");
|
||||
// assert!(predicate.eval(&[context]));
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// context.set("c", "x");
|
||||
// assert!(!predicate.eval(&[context]));
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// context.set("c", "d");
|
||||
// assert!(predicate.eval(&[context]));
|
||||
|
||||
// let predicate = KeyBindingContextPredicate::parse("!a").unwrap();
|
||||
// assert!(predicate.eval(&[KeyContext::default()]));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_child_predicate_eval() {
|
||||
// let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap();
|
||||
// let contexts = [
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["c", "d"]), // match this context
|
||||
// context_set(&["e", "f"]),
|
||||
// ];
|
||||
|
||||
// assert!(!predicate.eval(&contexts[..=0]));
|
||||
// assert!(predicate.eval(&contexts[..=1]));
|
||||
// assert!(!predicate.eval(&contexts[..=2]));
|
||||
|
||||
// let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap();
|
||||
// let contexts = [
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["c", "d"]),
|
||||
// context_set(&["e"]),
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["c"]),
|
||||
// context_set(&["e"]), // only match this context
|
||||
// context_set(&["f"]),
|
||||
// ];
|
||||
|
||||
// assert!(!predicate.eval(&contexts[..=0]));
|
||||
// assert!(!predicate.eval(&contexts[..=1]));
|
||||
// assert!(!predicate.eval(&contexts[..=2]));
|
||||
// assert!(!predicate.eval(&contexts[..=3]));
|
||||
// assert!(!predicate.eval(&contexts[..=4]));
|
||||
// assert!(predicate.eval(&contexts[..=5]));
|
||||
// assert!(!predicate.eval(&contexts[..=6]));
|
||||
|
||||
// fn context_set(names: &[&str]) -> KeyContext {
|
||||
// let mut keymap = KeyContext::default();
|
||||
// names.iter().for_each(|name| keymap.add(name.to_string()));
|
||||
// keymap
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_matcher() {
|
||||
// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
|
||||
// pub struct A(pub String);
|
||||
// impl_actions!(test, [A]);
|
||||
// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
|
||||
|
||||
// #[derive(Clone, Debug, Eq, PartialEq)]
|
||||
// struct ActionArg {
|
||||
// a: &'static str,
|
||||
// }
|
||||
|
||||
// let keymap = Keymap::new(vec![
|
||||
// KeyBinding::new("a", A("x".to_string()), Some("a")),
|
||||
// KeyBinding::new("b", B, Some("a")),
|
||||
// KeyBinding::new("a b", Ab, Some("a || b")),
|
||||
// KeyBinding::new("$", Dollar, Some("a")),
|
||||
// KeyBinding::new("\"", Quote, Some("a")),
|
||||
// KeyBinding::new("alt-s", Ess, Some("a")),
|
||||
// KeyBinding::new("ctrl-`", Backtick, Some("a")),
|
||||
// ]);
|
||||
|
||||
// let mut context_a = KeyContext::default();
|
||||
// context_a.add("a");
|
||||
|
||||
// let mut context_b = KeyContext::default();
|
||||
// context_b.add("b");
|
||||
|
||||
// let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
|
||||
|
||||
// // Basic match
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(A("x".to_string()))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Multi-keystroke match
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Ab)])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Failed matches don't interfere with matching subsequent keys
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::None
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(A("x".to_string()))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// let mut context_c = KeyContext::default();
|
||||
// context_c.add("c");
|
||||
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(
|
||||
// &Keystroke::parse("a").unwrap(),
|
||||
// &[context_c.clone(), context_b.clone()]
|
||||
// ),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Ab)])
|
||||
// );
|
||||
|
||||
// // handle Czech $ (option + 4 key)
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Dollar)])
|
||||
// );
|
||||
|
||||
// // handle Brazilian quote (quote key then space key)
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(
|
||||
// &Keystroke::parse("space->\"").unwrap(),
|
||||
// &[context_a.clone()]
|
||||
// ),
|
||||
// KeyMatch::Some(vec![Box::new(Quote)])
|
||||
// );
|
||||
|
||||
// // handle ctrl+` on a brazilian keyboard
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Backtick)])
|
||||
// );
|
||||
|
||||
// // handle alt-s on a US keyboard
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("alt-s->ß").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Ess)])
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
@ -6,4 +6,4 @@ mod matcher;
|
||||
pub use binding::*;
|
||||
pub use context::*;
|
||||
pub use keymap::*;
|
||||
pub use matcher::*;
|
||||
pub(crate) use matcher::*;
|
||||
|
@ -1,3 +1,7 @@
|
||||
//! The GPUI prelude is a collection of traits and types that are widely used
|
||||
//! throughout the library. It is recommended to import this prelude into your
|
||||
//! application to avoid having to import each trait individually.
|
||||
|
||||
pub use crate::{
|
||||
util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement,
|
||||
InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce,
|
||||
|
@ -10,12 +10,12 @@ pub(crate) type PointF = Point<f32>;
|
||||
#[allow(non_camel_case_types, unused)]
|
||||
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||
|
||||
pub type LayerId = u32;
|
||||
pub type DrawOrder = u32;
|
||||
pub(crate) type LayerId = u32;
|
||||
pub(crate) type DrawOrder = u32;
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[repr(C)]
|
||||
pub struct ViewId {
|
||||
pub(crate) struct ViewId {
|
||||
low_bits: u32,
|
||||
high_bits: u32,
|
||||
}
|
||||
@ -38,7 +38,7 @@ impl From<ViewId> for EntityId {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Scene {
|
||||
pub(crate) struct Scene {
|
||||
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
||||
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
|
||||
pub(crate) shadows: Vec<Shadow>,
|
||||
@ -429,7 +429,7 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub enum PrimitiveKind {
|
||||
pub(crate) enum PrimitiveKind {
|
||||
Shadow,
|
||||
#[default]
|
||||
Quad,
|
||||
@ -495,7 +495,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
pub(crate) struct Quad {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
@ -527,7 +527,7 @@ impl From<Quad> for Primitive {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Underline {
|
||||
pub(crate) struct Underline {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
@ -558,7 +558,7 @@ impl From<Underline> for Primitive {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Shadow {
|
||||
pub(crate) struct Shadow {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
@ -655,7 +655,7 @@ impl From<PolychromeSprite> for Primitive {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Surface {
|
||||
pub(crate) struct Surface {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
@ -685,6 +685,7 @@ impl From<Surface> for Primitive {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PathId(pub(crate) usize);
|
||||
|
||||
/// A line made up of a series of vertices and control points.
|
||||
#[derive(Debug)]
|
||||
pub struct Path<P: Clone + Default + Debug> {
|
||||
pub(crate) id: PathId,
|
||||
@ -701,6 +702,7 @@ pub struct Path<P: Clone + Default + Debug> {
|
||||
}
|
||||
|
||||
impl Path<Pixels> {
|
||||
/// Create a new path with the given starting point.
|
||||
pub fn new(start: Point<Pixels>) -> Self {
|
||||
Self {
|
||||
id: PathId(0),
|
||||
@ -720,6 +722,7 @@ impl Path<Pixels> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scale this path by the given factor.
|
||||
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||
Path {
|
||||
id: self.id,
|
||||
@ -740,6 +743,7 @@ impl Path<Pixels> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a straight line from the current point to the given point.
|
||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
if self.contour_count > 1 {
|
||||
@ -751,6 +755,7 @@ impl Path<Pixels> {
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
/// Draw a curve from the current point to the given point, using the given control point.
|
||||
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
if self.contour_count > 1 {
|
||||
@ -833,7 +838,7 @@ impl From<Path<ScaledPixels>> for Primitive {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct PathVertex<P: Clone + Default + Debug> {
|
||||
pub(crate) struct PathVertex<P: Clone + Default + Debug> {
|
||||
pub(crate) xy_position: Point<P>,
|
||||
pub(crate) st_position: Point<f32>,
|
||||
pub(crate) content_mask: ContentMask<P>,
|
||||
@ -850,4 +855,4 @@ impl PathVertex<Pixels> {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtlasId(pub(crate) usize);
|
||||
pub(crate) struct AtlasId(pub(crate) usize);
|
||||
|
@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{borrow::Borrow, sync::Arc};
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
/// A shared string is an immutable string that can be cheaply cloned in GPUI
|
||||
/// tasks. Essentially an abstraction over an Arc<str> and &'static str,
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
|
@ -147,12 +147,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a subscription created by GPUI. When dropped, the subscription
|
||||
/// is cancelled and the callback will no longer be invoked.
|
||||
#[must_use]
|
||||
pub struct Subscription {
|
||||
unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
/// Detaches the subscription from this handle. The callback will
|
||||
/// continue to be invoked until the views or models it has been
|
||||
/// subscribed to are dropped
|
||||
pub fn detach(mut self) {
|
||||
self.unsubscribe.take();
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ use anyhow::anyhow;
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
|
||||
#[derive(Clone, PartialEq, Hash, Eq)]
|
||||
pub struct RenderSvgParams {
|
||||
pub(crate) struct RenderSvgParams {
|
||||
pub(crate) path: SharedString,
|
||||
pub(crate) size: Size<DevicePixels>,
|
||||
}
|
||||
|
||||
pub struct SvgRenderer {
|
||||
pub(crate) struct SvgRenderer {
|
||||
asset_source: Arc<dyn AssetSource>,
|
||||
}
|
||||
|
||||
|
@ -229,6 +229,7 @@ impl TaffyLayoutEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/// A unique identifier for a layout node, generated when requesting a layout from Taffy
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct LayoutId(NodeId);
|
||||
@ -440,6 +441,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// The space available for an element to be laid out in
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
|
||||
pub enum AvailableSpace {
|
||||
/// The amount of space available is the specified number of pixels
|
||||
|
@ -34,6 +34,9 @@ use std::{
|
||||
panic::{self, RefUnwindSafe},
|
||||
};
|
||||
|
||||
/// Run the given test function with the configured parameters.
|
||||
/// This is intended for use with the `gpui::test` macro
|
||||
/// and generally should not be used directly.
|
||||
pub fn run_test(
|
||||
mut num_iterations: u64,
|
||||
max_retries: usize,
|
||||
@ -78,6 +81,7 @@ pub fn run_test(
|
||||
}
|
||||
}
|
||||
|
||||
/// A test struct for converting an observation callback into a stream.
|
||||
pub struct Observation<T> {
|
||||
rx: channel::Receiver<T>,
|
||||
_subscription: Subscription,
|
||||
|
@ -377,7 +377,7 @@ impl TextSystem {
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
||||
pub(crate) fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
||||
self.line_layout_cache.finish_frame(reused_views)
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ pub struct ShapedLine {
|
||||
}
|
||||
|
||||
impl ShapedLine {
|
||||
/// The length of the line in utf-8 bytes.
|
||||
pub fn len(&self) -> usize {
|
||||
self.layout.len
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user