See https://zed.dev/channel/gpui-536 Fixes https://github.com/zed-industries/zed/issues/9010 Fixes https://github.com/zed-industries/zed/issues/8883 Fixes https://github.com/zed-industries/zed/issues/8640 Fixes https://github.com/zed-industries/zed/issues/8598 Fixes https://github.com/zed-industries/zed/issues/8579 Fixes https://github.com/zed-industries/zed/issues/8363 Fixes https://github.com/zed-industries/zed/issues/8207 ### Problem After transitioning Zed to GPUI 2, we started noticing that interacting with the mouse on many UI elements would lead to a pretty annoying flicker. The main issue with the old approach was that hover state was calculated based on the previous frame. That is, when computing whether a given element was hovered in the current frame, we would use information about the same element in the previous frame. However, inspecting the previous frame tells us very little about what should be hovered in the current frame, as elements in the current frame may have changed significantly. ### Solution This pull request's main contribution is the introduction of a new `after_layout` phase when redrawing the window. The key idea is that we'll give every element a chance to register a hitbox (see `ElementContext::insert_hitbox`) before painting anything. Then, during the `paint` phase, elements can determine whether they're the topmost and draw their hover state accordingly. We are also removing the ability to give an arbitrary z-index to elements. Instead, we will follow the much simpler painter's algorithm. That is, an element that gets painted after will be drawn on top of an element that got painted earlier. Elements can still escape their current "stacking context" by using the new `ElementContext::defer_draw` method (see `Overlay` for an example). Elements drawn using this method will still be logically considered as being children of their original parent (for keybinding, focus and cache invalidation purposes) but their layout and paint passes will be deferred until the currently-drawn element is done. With these changes we also reworked geometry batching within the `Scene`. The new approach uses an AABB tree to determine geometry occlusion, which allows the GPU to render non-overlapping geometry in parallel. ### Performance Performance is slightly better than on `main` even though this new approach is more correct and we're maintaining an extra data structure (the AABB tree).  Release Notes: - Fixed a bug that was causing popovers to flicker. --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: Thorsten <thorsten@zed.dev>
767 lines
24 KiB
Rust
767 lines
24 KiB
Rust
// todo(linux): remove
|
|
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
|
// todo("windows"): remove
|
|
#![cfg_attr(windows, allow(dead_code))]
|
|
|
|
mod app_menu;
|
|
mod keystroke;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod linux;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
mod mac;
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))]
|
|
mod blade;
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
mod test;
|
|
|
|
#[cfg(target_os = "windows")]
|
|
mod windows;
|
|
|
|
use crate::{
|
|
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
|
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
|
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene,
|
|
SharedString, Size, Task, TaskLabel, WindowContext,
|
|
};
|
|
use anyhow::Result;
|
|
use async_task::Runnable;
|
|
use futures::channel::oneshot;
|
|
use parking::Unparker;
|
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
|
use seahash::SeaHasher;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::borrow::Cow;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::time::Duration;
|
|
use std::{
|
|
any::Any,
|
|
fmt::{self, Debug},
|
|
ops::Range,
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
sync::Arc,
|
|
};
|
|
use uuid::Uuid;
|
|
|
|
pub use app_menu::*;
|
|
pub use keystroke::*;
|
|
#[cfg(target_os = "linux")]
|
|
pub(crate) use linux::*;
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) use mac::*;
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
pub(crate) use test::*;
|
|
use time::UtcOffset;
|
|
pub use util::SemanticVersion;
|
|
#[cfg(target_os = "windows")]
|
|
pub(crate) use windows::*;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
|
Rc::new(MacPlatform::new())
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
|
Rc::new(LinuxPlatform::new())
|
|
}
|
|
// todo("windows")
|
|
#[cfg(target_os = "windows")]
|
|
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
|
Rc::new(WindowsPlatform::new())
|
|
}
|
|
|
|
pub(crate) trait Platform: 'static {
|
|
fn background_executor(&self) -> BackgroundExecutor;
|
|
fn foreground_executor(&self) -> ForegroundExecutor;
|
|
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
|
|
|
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
|
|
fn quit(&self);
|
|
fn restart(&self);
|
|
fn activate(&self, ignoring_other_apps: bool);
|
|
fn hide(&self);
|
|
fn hide_other_apps(&self);
|
|
fn unhide_other_apps(&self);
|
|
|
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
|
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
|
fn active_window(&self) -> Option<AnyWindowHandle>;
|
|
fn open_window(
|
|
&self,
|
|
handle: AnyWindowHandle,
|
|
options: WindowOptions,
|
|
) -> Box<dyn PlatformWindow>;
|
|
|
|
/// Returns the appearance of the application's windows.
|
|
fn window_appearance(&self) -> WindowAppearance;
|
|
|
|
fn open_url(&self, url: &str);
|
|
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
|
fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
|
|
|
|
fn prompt_for_paths(
|
|
&self,
|
|
options: PathPromptOptions,
|
|
) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
|
|
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
|
|
fn reveal_path(&self, path: &Path);
|
|
|
|
fn on_become_active(&self, callback: Box<dyn FnMut()>);
|
|
fn on_resign_active(&self, callback: Box<dyn FnMut()>);
|
|
fn on_quit(&self, callback: Box<dyn FnMut()>);
|
|
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
|
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
|
|
|
|
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
|
|
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
|
|
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
|
|
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
|
|
|
|
fn os_name(&self) -> &'static str;
|
|
fn os_version(&self) -> Result<SemanticVersion>;
|
|
fn app_version(&self) -> Result<SemanticVersion>;
|
|
fn app_path(&self) -> Result<PathBuf>;
|
|
fn local_timezone(&self) -> UtcOffset;
|
|
fn double_click_interval(&self) -> Duration;
|
|
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
|
|
|
|
fn set_cursor_style(&self, style: CursorStyle);
|
|
fn should_auto_hide_scrollbars(&self) -> bool;
|
|
|
|
fn write_to_clipboard(&self, item: ClipboardItem);
|
|
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
|
|
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
|
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
|
|
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
|
|
}
|
|
|
|
/// A handle to a platform's display, e.g. a monitor or laptop screen.
|
|
pub trait PlatformDisplay: Send + Sync + Debug {
|
|
/// Get the ID for this display
|
|
fn id(&self) -> DisplayId;
|
|
|
|
/// Returns a stable identifier for this display that can be persisted and used
|
|
/// across system restarts.
|
|
fn uuid(&self) -> Result<Uuid>;
|
|
|
|
/// Get the bounds for this display
|
|
fn bounds(&self) -> Bounds<GlobalPixels>;
|
|
}
|
|
|
|
/// An opaque identifier for a hardware display
|
|
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
|
pub struct DisplayId(pub(crate) u32);
|
|
|
|
impl Debug for DisplayId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "DisplayId({})", self.0)
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for DisplayId {}
|
|
|
|
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|
fn bounds(&self) -> WindowBounds;
|
|
fn content_size(&self) -> Size<Pixels>;
|
|
fn scale_factor(&self) -> f32;
|
|
fn titlebar_height(&self) -> Pixels;
|
|
fn appearance(&self) -> WindowAppearance;
|
|
fn display(&self) -> Rc<dyn PlatformDisplay>;
|
|
fn mouse_position(&self) -> Point<Pixels>;
|
|
fn modifiers(&self) -> Modifiers;
|
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
|
fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
|
|
fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
|
|
fn prompt(
|
|
&self,
|
|
level: PromptLevel,
|
|
msg: &str,
|
|
detail: Option<&str>,
|
|
answers: &[&str],
|
|
) -> Option<oneshot::Receiver<usize>>;
|
|
fn activate(&self);
|
|
fn set_title(&mut self, title: &str);
|
|
fn set_edited(&mut self, edited: bool);
|
|
fn show_character_palette(&self);
|
|
fn minimize(&self);
|
|
fn zoom(&self);
|
|
fn toggle_full_screen(&self);
|
|
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
|
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
|
|
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
|
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
|
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
|
|
fn on_moved(&self, callback: Box<dyn FnMut()>);
|
|
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
|
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
|
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
|
fn draw(&self, scene: &Scene);
|
|
|
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// This type is public so that our test macro can generate and use it, but it should not
|
|
/// be considered part of our public API.
|
|
#[doc(hidden)]
|
|
pub trait PlatformDispatcher: Send + Sync {
|
|
fn is_main_thread(&self) -> bool;
|
|
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
|
|
fn dispatch_on_main_thread(&self, runnable: Runnable);
|
|
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
|
fn tick(&self, background_only: bool) -> bool;
|
|
fn park(&self);
|
|
fn unparker(&self) -> Unparker;
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
fn as_test(&self) -> Option<&TestDispatcher> {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) trait PlatformTextSystem: Send + Sync {
|
|
fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
|
|
fn all_font_names(&self) -> Vec<String>;
|
|
fn all_font_families(&self) -> Vec<String>;
|
|
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
|
|
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
|
|
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
|
|
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
|
|
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
|
|
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
|
|
fn rasterize_glyph(
|
|
&self,
|
|
params: &RenderGlyphParams,
|
|
raster_bounds: Bounds<DevicePixels>,
|
|
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
|
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
|
fn wrap_line(
|
|
&self,
|
|
text: &str,
|
|
font_id: FontId,
|
|
font_size: Pixels,
|
|
width: Pixels,
|
|
) -> Vec<usize>;
|
|
}
|
|
|
|
/// Basic metadata about the current application and operating system.
|
|
#[derive(Clone, Debug)]
|
|
pub struct AppMetadata {
|
|
/// The name of the current operating system
|
|
pub os_name: &'static str,
|
|
|
|
/// The operating system's version
|
|
pub os_version: Option<SemanticVersion>,
|
|
|
|
/// The current version of the application
|
|
pub app_version: Option<SemanticVersion>,
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
|
pub(crate) enum AtlasKey {
|
|
Glyph(RenderGlyphParams),
|
|
Svg(RenderSvgParams),
|
|
Image(RenderImageParams),
|
|
}
|
|
|
|
impl AtlasKey {
|
|
pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
|
|
match self {
|
|
AtlasKey::Glyph(params) => {
|
|
if params.is_emoji {
|
|
AtlasTextureKind::Polychrome
|
|
} else {
|
|
AtlasTextureKind::Monochrome
|
|
}
|
|
}
|
|
AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
|
|
AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RenderGlyphParams> for AtlasKey {
|
|
fn from(params: RenderGlyphParams) -> Self {
|
|
Self::Glyph(params)
|
|
}
|
|
}
|
|
|
|
impl From<RenderSvgParams> for AtlasKey {
|
|
fn from(params: RenderSvgParams) -> Self {
|
|
Self::Svg(params)
|
|
}
|
|
}
|
|
|
|
impl From<RenderImageParams> for AtlasKey {
|
|
fn from(params: RenderImageParams) -> Self {
|
|
Self::Image(params)
|
|
}
|
|
}
|
|
|
|
pub(crate) trait PlatformAtlas: Send + Sync {
|
|
fn get_or_insert_with<'a>(
|
|
&self,
|
|
key: &AtlasKey,
|
|
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
|
|
) -> Result<AtlasTile>;
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub(crate) struct AtlasTile {
|
|
pub(crate) texture_id: AtlasTextureId,
|
|
pub(crate) tile_id: TileId,
|
|
pub(crate) padding: u32,
|
|
pub(crate) bounds: Bounds<DevicePixels>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
#[repr(C)]
|
|
pub(crate) struct AtlasTextureId {
|
|
// We use u32 instead of usize for Metal Shader Language compatibility
|
|
pub(crate) index: u32,
|
|
pub(crate) kind: AtlasTextureKind,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
#[repr(C)]
|
|
pub(crate) enum AtlasTextureKind {
|
|
Monochrome = 0,
|
|
Polychrome = 1,
|
|
Path = 2,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[repr(C)]
|
|
pub(crate) struct TileId(pub(crate) u32);
|
|
|
|
impl From<etagere::AllocId> for TileId {
|
|
fn from(id: etagere::AllocId) -> Self {
|
|
Self(id.serialize())
|
|
}
|
|
}
|
|
|
|
impl From<TileId> for etagere::AllocId {
|
|
fn from(id: TileId) -> Self {
|
|
Self::deserialize(id.0)
|
|
}
|
|
}
|
|
|
|
pub(crate) struct PlatformInputHandler {
|
|
cx: AsyncWindowContext,
|
|
handler: Box<dyn InputHandler>,
|
|
}
|
|
|
|
impl PlatformInputHandler {
|
|
pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
|
|
Self { cx, handler }
|
|
}
|
|
|
|
fn selected_text_range(&mut self) -> Option<Range<usize>> {
|
|
self.cx
|
|
.update(|cx| self.handler.selected_text_range(cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
fn marked_text_range(&mut self) -> Option<Range<usize>> {
|
|
self.cx
|
|
.update(|cx| self.handler.marked_text_range(cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
|
self.cx
|
|
.update(|cx| self.handler.text_for_range(range_utf16, cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
|
self.cx
|
|
.update(|cx| {
|
|
self.handler
|
|
.replace_text_in_range(replacement_range, text, cx);
|
|
})
|
|
.ok();
|
|
}
|
|
|
|
fn replace_and_mark_text_in_range(
|
|
&mut self,
|
|
range_utf16: Option<Range<usize>>,
|
|
new_text: &str,
|
|
new_selected_range: Option<Range<usize>>,
|
|
) {
|
|
self.cx
|
|
.update(|cx| {
|
|
self.handler.replace_and_mark_text_in_range(
|
|
range_utf16,
|
|
new_text,
|
|
new_selected_range,
|
|
cx,
|
|
)
|
|
})
|
|
.ok();
|
|
}
|
|
|
|
fn unmark_text(&mut self) {
|
|
self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
|
|
}
|
|
|
|
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
|
self.cx
|
|
.update(|cx| self.handler.bounds_for_range(range_utf16, cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
|
|
self.handler.replace_text_in_range(None, input, cx);
|
|
}
|
|
}
|
|
|
|
/// Zed's interface for handling text input from the platform's IME system
|
|
/// This is currently a 1:1 exposure of the NSTextInputClient API:
|
|
///
|
|
/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
|
|
pub trait InputHandler: 'static {
|
|
/// Get the range of the user's currently selected text, if any
|
|
/// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
|
|
///
|
|
/// Return value is in terms of UTF-16 characters, from 0 to the length of the document
|
|
fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
|
|
|
/// Get the range of the currently marked text, if any
|
|
/// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
|
|
///
|
|
/// Return value is in terms of UTF-16 characters, from 0 to the length of the document
|
|
fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
|
|
|
/// Get the text for the given document range in UTF-16 characters
|
|
/// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
|
|
///
|
|
/// range_utf16 is in terms of UTF-16 characters
|
|
fn text_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
cx: &mut WindowContext,
|
|
) -> Option<String>;
|
|
|
|
/// Replace the text in the given document range with the given text
|
|
/// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
|
|
///
|
|
/// replacement_range is in terms of UTF-16 characters
|
|
fn replace_text_in_range(
|
|
&mut self,
|
|
replacement_range: Option<Range<usize>>,
|
|
text: &str,
|
|
cx: &mut WindowContext,
|
|
);
|
|
|
|
/// Replace the text in the given document range with the given text,
|
|
/// and mark the given text as part of of an IME 'composing' state
|
|
/// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
|
|
///
|
|
/// range_utf16 is in terms of UTF-16 characters
|
|
/// new_selected_range is in terms of UTF-16 characters
|
|
fn replace_and_mark_text_in_range(
|
|
&mut self,
|
|
range_utf16: Option<Range<usize>>,
|
|
new_text: &str,
|
|
new_selected_range: Option<Range<usize>>,
|
|
cx: &mut WindowContext,
|
|
);
|
|
|
|
/// Remove the IME 'composing' state from the document
|
|
/// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
|
|
fn unmark_text(&mut self, cx: &mut WindowContext);
|
|
|
|
/// Get the bounds of the given document range in screen coordinates
|
|
/// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
|
|
///
|
|
/// This is used for positioning the IME candidate window
|
|
fn bounds_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
cx: &mut WindowContext,
|
|
) -> Option<Bounds<Pixels>>;
|
|
}
|
|
|
|
/// The variables that can be configured when creating a new window
|
|
#[derive(Debug)]
|
|
pub struct WindowOptions {
|
|
/// The initial bounds of the window
|
|
pub bounds: WindowBounds,
|
|
|
|
/// The titlebar configuration of the window
|
|
pub titlebar: Option<TitlebarOptions>,
|
|
|
|
/// Whether the window should be centered on the screen
|
|
pub center: bool,
|
|
|
|
/// Whether the window should be focused when created
|
|
pub focus: bool,
|
|
|
|
/// Whether the window should be shown when created
|
|
pub show: bool,
|
|
|
|
/// The kind of window to create
|
|
pub kind: WindowKind,
|
|
|
|
/// Whether the window should be movable by the user
|
|
pub is_movable: bool,
|
|
|
|
/// The display to create the window on
|
|
pub display_id: Option<DisplayId>,
|
|
}
|
|
|
|
impl Default for WindowOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
bounds: WindowBounds::default(),
|
|
titlebar: Some(TitlebarOptions {
|
|
title: Default::default(),
|
|
appears_transparent: Default::default(),
|
|
traffic_light_position: Default::default(),
|
|
}),
|
|
center: false,
|
|
focus: true,
|
|
show: true,
|
|
kind: WindowKind::Normal,
|
|
is_movable: true,
|
|
display_id: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The options that can be configured for a window's titlebar
|
|
#[derive(Debug, Default)]
|
|
pub struct TitlebarOptions {
|
|
/// The initial title of the window
|
|
pub title: Option<SharedString>,
|
|
|
|
/// Whether the titlebar should appear transparent
|
|
pub appears_transparent: bool,
|
|
|
|
/// The position of the macOS traffic light buttons
|
|
pub traffic_light_position: Option<Point<Pixels>>,
|
|
}
|
|
|
|
/// The kind of window to create
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum WindowKind {
|
|
/// A normal application window
|
|
Normal,
|
|
|
|
/// A window that appears above all other windows, usually used for alerts or popups
|
|
/// use sparingly!
|
|
PopUp,
|
|
}
|
|
|
|
/// Which bounds algorithm to use for the initial size a window
|
|
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
|
pub enum WindowBounds {
|
|
/// The window should be full screen, on macOS this corresponds to the full screen feature
|
|
Fullscreen,
|
|
|
|
/// Make the window as large as the current display's size.
|
|
#[default]
|
|
Maximized,
|
|
|
|
/// Set the window to the given size in pixels
|
|
Fixed(Bounds<GlobalPixels>),
|
|
}
|
|
|
|
/// The appearance of the window, as defined by the operating system.
|
|
///
|
|
/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
|
|
/// values.
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum WindowAppearance {
|
|
/// A light appearance.
|
|
///
|
|
/// On macOS, this corresponds to the `aqua` appearance.
|
|
Light,
|
|
|
|
/// A light appearance with vibrant colors.
|
|
///
|
|
/// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
|
|
VibrantLight,
|
|
|
|
/// A dark appearance.
|
|
///
|
|
/// On macOS, this corresponds to the `darkAqua` appearance.
|
|
Dark,
|
|
|
|
/// A dark appearance with vibrant colors.
|
|
///
|
|
/// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
|
|
VibrantDark,
|
|
}
|
|
|
|
impl Default for WindowAppearance {
|
|
fn default() -> Self {
|
|
Self::Light
|
|
}
|
|
}
|
|
|
|
/// The options that can be configured for a file dialog prompt
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct PathPromptOptions {
|
|
/// Should the prompt allow files to be selected?
|
|
pub files: bool,
|
|
/// Should the prompt allow directories to be selected?
|
|
pub directories: bool,
|
|
/// Should the prompt allow multiple files to be selected?
|
|
pub multiple: bool,
|
|
}
|
|
|
|
/// What kind of prompt styling to show
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum PromptLevel {
|
|
/// A prompt that is shown when the user should be notified of something
|
|
Info,
|
|
|
|
/// A prompt that is shown when the user needs to be warned of a potential problem
|
|
Warning,
|
|
|
|
/// A prompt that is shown when a critical problem has occurred
|
|
Critical,
|
|
}
|
|
|
|
/// The style of the cursor (pointer)
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum CursorStyle {
|
|
/// The default cursor
|
|
Arrow,
|
|
|
|
/// A text input cursor
|
|
/// corresponds to the CSS cursor value `text`
|
|
IBeam,
|
|
|
|
/// A crosshair cursor
|
|
/// corresponds to the CSS cursor value `crosshair`
|
|
Crosshair,
|
|
|
|
/// A closed hand cursor
|
|
/// corresponds to the CSS cursor value `grabbing`
|
|
ClosedHand,
|
|
|
|
/// An open hand cursor
|
|
/// corresponds to the CSS cursor value `grab`
|
|
OpenHand,
|
|
|
|
/// A pointing hand cursor
|
|
/// corresponds to the CSS cursor value `pointer`
|
|
PointingHand,
|
|
|
|
/// A resize left cursor
|
|
/// corresponds to the CSS cursor value `w-resize`
|
|
ResizeLeft,
|
|
|
|
/// A resize right cursor
|
|
/// corresponds to the CSS cursor value `e-resize`
|
|
ResizeRight,
|
|
|
|
/// A resize cursor to the left and right
|
|
/// corresponds to the CSS cursor value `col-resize`
|
|
ResizeLeftRight,
|
|
|
|
/// A resize up cursor
|
|
/// corresponds to the CSS cursor value `n-resize`
|
|
ResizeUp,
|
|
|
|
/// A resize down cursor
|
|
/// corresponds to the CSS cursor value `s-resize`
|
|
ResizeDown,
|
|
|
|
/// A resize cursor directing up and down
|
|
/// corresponds to the CSS cursor value `row-resize`
|
|
ResizeUpDown,
|
|
|
|
/// A cursor indicating that something will disappear if moved here
|
|
/// Does not correspond to a CSS cursor value
|
|
DisappearingItem,
|
|
|
|
/// A text input cursor for vertical layout
|
|
/// corresponds to the CSS cursor value `vertical-text`
|
|
IBeamCursorForVerticalLayout,
|
|
|
|
/// A cursor indicating that the operation is not allowed
|
|
/// corresponds to the CSS cursor value `not-allowed`
|
|
OperationNotAllowed,
|
|
|
|
/// A cursor indicating that the operation will result in a link
|
|
/// corresponds to the CSS cursor value `alias`
|
|
DragLink,
|
|
|
|
/// A cursor indicating that the operation will result in a copy
|
|
/// corresponds to the CSS cursor value `copy`
|
|
DragCopy,
|
|
|
|
/// A cursor indicating that the operation will result in a context menu
|
|
/// corresponds to the CSS cursor value `context-menu`
|
|
ContextualMenu,
|
|
}
|
|
|
|
impl Default for CursorStyle {
|
|
fn default() -> Self {
|
|
Self::Arrow
|
|
}
|
|
}
|
|
|
|
/// A clipboard item that should be copied to the clipboard
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct ClipboardItem {
|
|
pub(crate) text: String,
|
|
pub(crate) metadata: Option<String>,
|
|
}
|
|
|
|
impl ClipboardItem {
|
|
/// Create a new clipboard item with the given text
|
|
pub fn new(text: String) -> Self {
|
|
Self {
|
|
text,
|
|
metadata: None,
|
|
}
|
|
}
|
|
|
|
/// Create a new clipboard item with the given text and metadata
|
|
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
|
|
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
|
|
self
|
|
}
|
|
|
|
/// Get the text of the clipboard item
|
|
pub fn text(&self) -> &String {
|
|
&self.text
|
|
}
|
|
|
|
/// Get the metadata of the clipboard item
|
|
pub fn metadata<T>(&self) -> Option<T>
|
|
where
|
|
T: for<'a> Deserialize<'a>,
|
|
{
|
|
self.metadata
|
|
.as_ref()
|
|
.and_then(|m| serde_json::from_str(m).ok())
|
|
}
|
|
|
|
pub(crate) fn text_hash(text: &str) -> u64 {
|
|
let mut hasher = SeaHasher::new();
|
|
text.hash(&mut hasher);
|
|
hasher.finish()
|
|
}
|
|
}
|