Merge branch 'main' into jk

This commit is contained in:
Conrad Irwin 2024-01-21 20:36:18 -07:00
commit 9d261cf859
30 changed files with 536 additions and 426 deletions

View File

@ -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
}
}

View File

@ -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

View File

@ -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.

View File

@ -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
//!

View File

@ -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

View File

@ -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,
}

View File

@ -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,

View File

@ -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

View File

@ -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(),

View File

@ -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

View File

@ -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::<

View File

@ -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>;
}

View File

@ -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>>>,
}

View File

@ -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>,

View File

@ -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),

View File

@ -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>,

View File

@ -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()
}

View File

@ -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;

View File

@ -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 {

View File

@ -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)])
// );
// }
// }

View File

@ -6,4 +6,4 @@ mod matcher;
pub use binding::*;
pub use context::*;
pub use keymap::*;
pub use matcher::*;
pub(crate) use matcher::*;

View File

@ -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,

View File

@ -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);

View File

@ -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>);

View File

@ -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();
}

View File

@ -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>,
}

View File

@ -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

View File

@ -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,

View File

@ -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)
}

View File

@ -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
}