mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Merge branch 'gpui2' into marshall/gpui2-playground
This commit is contained in:
commit
d14dc35efe
834
Cargo.lock
generated
834
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,8 @@ members = [
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui2",
|
||||
"crates/gpui2_macros",
|
||||
"crates/gpui3",
|
||||
"crates/gpui3_macros",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
@ -65,6 +67,7 @@ members = [
|
||||
"crates/sqlez_macros",
|
||||
"crates/feature_flags",
|
||||
"crates/storybook",
|
||||
"crates/storybook2",
|
||||
"crates/sum_tree",
|
||||
"crates/terminal",
|
||||
"crates/text",
|
||||
|
@ -3607,7 +3607,7 @@ impl<V> BorrowWindowContext for EventContext<'_, '_, '_, V> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Reference<'a, T> {
|
||||
pub enum Reference<'a, T> {
|
||||
Immutable(&'a T),
|
||||
Mutable(&'a mut T),
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::scene::{Path, PathVertex};
|
||||
use crate::{color::Color, json::ToJson};
|
||||
use derive_more::Neg;
|
||||
pub use pathfinder_geometry::*;
|
||||
use rect::RectF;
|
||||
use refineable::Refineable;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde_json::json;
|
||||
use std::fmt::Debug;
|
||||
use vector::{vec2f, Vector2F};
|
||||
|
||||
pub struct PathBuilder {
|
||||
@ -167,6 +167,15 @@ pub struct Size<T: Clone + Default + Debug> {
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn full() -> Self {
|
||||
Self {
|
||||
width: relative(1.),
|
||||
height: relative(1.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
|
||||
where
|
||||
S: Into<T>,
|
||||
@ -194,8 +203,8 @@ where
|
||||
impl Size<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
width: pixels(0.).into(),
|
||||
height: pixels(0.).into(),
|
||||
width: px(0.),
|
||||
height: px(0.),
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,17 +244,6 @@ pub struct Edges<T: Clone + Default + Debug> {
|
||||
pub left: T,
|
||||
}
|
||||
|
||||
impl<T: Clone + Default + Debug> Edges<T> {
|
||||
pub fn uniform(value: T) -> Self {
|
||||
Self {
|
||||
top: value.clone(),
|
||||
right: value.clone(),
|
||||
bottom: value.clone(),
|
||||
left: value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
@ -258,10 +256,10 @@ impl Edges<Length> {
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: pixels(0.).into(),
|
||||
right: pixels(0.).into(),
|
||||
bottom: pixels(0.).into(),
|
||||
left: pixels(0.).into(),
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,10 +279,10 @@ impl Edges<Length> {
|
||||
impl Edges<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: pixels(0.).into(),
|
||||
right: pixels(0.).into(),
|
||||
bottom: pixels(0.).into(),
|
||||
left: pixels(0.).into(),
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,10 +299,10 @@ impl Edges<DefiniteLength> {
|
||||
impl Edges<AbsoluteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: pixels(0.),
|
||||
right: pixels(0.),
|
||||
bottom: pixels(0.),
|
||||
left: pixels(0.),
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,7 +331,7 @@ impl Edges<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Neg)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum AbsoluteLength {
|
||||
Pixels(f32),
|
||||
Rems(f32),
|
||||
@ -371,7 +369,7 @@ impl Default for AbsoluteLength {
|
||||
}
|
||||
|
||||
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
||||
#[derive(Clone, Copy, Neg)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DefiniteLength {
|
||||
Absolute(AbsoluteLength),
|
||||
Relative(f32), // 0. to 1.
|
||||
@ -415,7 +413,7 @@ impl Default for DefiniteLength {
|
||||
}
|
||||
|
||||
/// A length that can be defined in pixels, rems, percent of parent, or auto.
|
||||
#[derive(Clone, Copy, Neg)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Length {
|
||||
Definite(DefiniteLength),
|
||||
Auto,
|
||||
@ -430,16 +428,20 @@ impl std::fmt::Debug for Length {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relative(fraction: f32) -> DefiniteLength {
|
||||
DefiniteLength::Relative(fraction)
|
||||
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
|
||||
DefiniteLength::Relative(fraction).into()
|
||||
}
|
||||
|
||||
pub fn rems(rems: f32) -> AbsoluteLength {
|
||||
AbsoluteLength::Rems(rems)
|
||||
pub fn rems<T: From<AbsoluteLength>>(rems: f32) -> T {
|
||||
AbsoluteLength::Rems(rems).into()
|
||||
}
|
||||
|
||||
pub fn pixels(pixels: f32) -> AbsoluteLength {
|
||||
AbsoluteLength::Pixels(pixels)
|
||||
pub fn px<T: From<AbsoluteLength>>(pixels: f32) -> T {
|
||||
AbsoluteLength::Pixels(pixels).into()
|
||||
}
|
||||
|
||||
pub fn pixels<T: From<AbsoluteLength>>(pixels: f32) -> T {
|
||||
AbsoluteLength::Pixels(pixels).into()
|
||||
}
|
||||
|
||||
pub fn auto() -> Length {
|
||||
|
@ -22,8 +22,8 @@ use std::{
|
||||
};
|
||||
|
||||
pub struct TextLayoutCache {
|
||||
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
||||
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
||||
prev_frame: Mutex<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
|
||||
curr_frame: RwLock<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ impl TextLayoutCache {
|
||||
font_size: f32,
|
||||
runs: &'a [(usize, RunStyle)],
|
||||
) -> Line {
|
||||
let key = &CacheKeyRef {
|
||||
let key = &BorrowedCacheKey {
|
||||
text,
|
||||
font_size: OrderedFloat(font_size),
|
||||
runs,
|
||||
@ -72,7 +72,7 @@ impl TextLayoutCache {
|
||||
Line::new(layout, runs)
|
||||
} else {
|
||||
let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
|
||||
let key = CacheKeyValue {
|
||||
let key = OwnedCacheKey {
|
||||
text: text.into(),
|
||||
font_size: OrderedFloat(font_size),
|
||||
runs: SmallVec::from(runs),
|
||||
@ -84,7 +84,7 @@ impl TextLayoutCache {
|
||||
}
|
||||
|
||||
trait CacheKey {
|
||||
fn key(&self) -> CacheKeyRef;
|
||||
fn key(&self) -> BorrowedCacheKey;
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for (dyn CacheKey + 'a) {
|
||||
@ -102,15 +102,15 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
struct CacheKeyValue {
|
||||
struct OwnedCacheKey {
|
||||
text: String,
|
||||
font_size: OrderedFloat<f32>,
|
||||
runs: SmallVec<[(usize, RunStyle); 1]>,
|
||||
}
|
||||
|
||||
impl CacheKey for CacheKeyValue {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
CacheKeyRef {
|
||||
impl CacheKey for OwnedCacheKey {
|
||||
fn key(&self) -> BorrowedCacheKey {
|
||||
BorrowedCacheKey {
|
||||
text: self.text.as_str(),
|
||||
font_size: self.font_size,
|
||||
runs: self.runs.as_slice(),
|
||||
@ -118,38 +118,38 @@ impl CacheKey for CacheKeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CacheKeyValue {
|
||||
impl PartialEq for OwnedCacheKey {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key().eq(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CacheKeyValue {
|
||||
impl Hash for OwnedCacheKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
|
||||
impl<'a> Borrow<dyn CacheKey + 'a> for OwnedCacheKey {
|
||||
fn borrow(&self) -> &(dyn CacheKey + 'a) {
|
||||
self as &dyn CacheKey
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct CacheKeyRef<'a> {
|
||||
struct BorrowedCacheKey<'a> {
|
||||
text: &'a str,
|
||||
font_size: OrderedFloat<f32>,
|
||||
runs: &'a [(usize, RunStyle)],
|
||||
}
|
||||
|
||||
impl<'a> CacheKey for CacheKeyRef<'a> {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
impl<'a> CacheKey for BorrowedCacheKey<'a> {
|
||||
fn key(&self) -> BorrowedCacheKey {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for CacheKeyRef<'a> {
|
||||
impl<'a> PartialEq for BorrowedCacheKey<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.text == other.text
|
||||
&& self.font_size == other.font_size
|
||||
@ -162,7 +162,7 @@ impl<'a> PartialEq for CacheKeyRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for CacheKeyRef<'a> {
|
||||
impl<'a> Hash for BorrowedCacheKey<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.text.hash(state);
|
||||
self.font_size.hash(state);
|
||||
|
@ -12,11 +12,21 @@ use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
|
||||
impl<V: 'static> IntoElement<V> for ArcCow<'static, str> {
|
||||
type Element = Text;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
Text { text: self.into() }
|
||||
Text { text: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> IntoElement<V> for &'static str {
|
||||
type Element = Text;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
Text {
|
||||
text: ArcCow::from(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ pub mod interactive;
|
||||
pub mod style;
|
||||
pub mod view;
|
||||
pub mod view_context;
|
||||
pub mod view_handle;
|
||||
|
||||
pub use color::*;
|
||||
pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};
|
||||
|
@ -22,6 +22,8 @@ use gpui2_macros::styleable_helpers;
|
||||
use refineable::{Refineable, RefinementCascade};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type StyleCascade = RefinementCascade<Style>;
|
||||
|
||||
#[derive(Clone, Refineable, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Style {
|
||||
@ -129,7 +131,7 @@ impl Style {
|
||||
color: self.text_color.map(Into::into),
|
||||
font_family: self.font_family.clone(),
|
||||
font_size: self.font_size.map(|size| size * cx.rem_size()),
|
||||
font_weight: self.font_weight,
|
||||
font_weight: self.font_weight.map(Into::into),
|
||||
font_style: self.font_style,
|
||||
underline: None,
|
||||
})
|
||||
|
@ -3,7 +3,10 @@ use std::{any::TypeId, rc::Rc};
|
||||
use crate::{element::LayoutId, style::Style};
|
||||
use anyhow::{anyhow, Result};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
|
||||
use gpui::{
|
||||
geometry::Size, scene::EventHandler, AnyWindowHandle, BorrowWindowContext, EventContext,
|
||||
Layout, MeasureParams, WindowContext,
|
||||
};
|
||||
pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
@ -77,3 +80,35 @@ impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
|
||||
.computed_layout(layout_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V: 'static> BorrowWindowContext for ViewContext<'a, 'b, 'c, V> {
|
||||
type Result<T> = T;
|
||||
|
||||
fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
|
||||
where
|
||||
F: FnOnce(&WindowContext) -> T,
|
||||
{
|
||||
self.legacy_cx.read_window(window, f)
|
||||
}
|
||||
|
||||
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&WindowContext) -> Option<T>,
|
||||
{
|
||||
self.legacy_cx.read_window_optional(window, f)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
|
||||
where
|
||||
F: FnOnce(&mut WindowContext) -> T,
|
||||
{
|
||||
self.legacy_cx.update_window(window, f)
|
||||
}
|
||||
|
||||
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut WindowContext) -> Option<T>,
|
||||
{
|
||||
self.legacy_cx.update_window_optional(window, f)
|
||||
}
|
||||
}
|
||||
|
60
crates/gpui2/src/view_handle.rs
Normal file
60
crates/gpui2/src/view_handle.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use crate::{style::Style, Element, IntoElement, ViewContext};
|
||||
use gpui::{
|
||||
geometry::{Point, Size},
|
||||
taffy::style::Overflow,
|
||||
AnyElement, View, ViewHandle,
|
||||
};
|
||||
|
||||
impl<ParentView: 'static, ChildView: View> Element<ParentView> for ViewHandle<ChildView> {
|
||||
type PaintState = AnyElement<ChildView>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut ParentView,
|
||||
cx: &mut ViewContext<ParentView>,
|
||||
) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let layout_id = cx.add_layout_node(
|
||||
Style {
|
||||
overflow: Point {
|
||||
x: Overflow::Hidden,
|
||||
y: Overflow::Hidden,
|
||||
},
|
||||
size: Size::full(),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)?;
|
||||
let element = self.update(cx, |view, cx| view.render(cx));
|
||||
Ok((layout_id, element))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut ParentView,
|
||||
parent_origin: gpui::geometry::vector::Vector2F,
|
||||
layout: &gpui::Layout,
|
||||
element: &mut AnyElement<ChildView>,
|
||||
cx: &mut ViewContext<ParentView>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.update(cx, |view, cx| {
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
element.layout(gpui::SizeConstraint::strict(bounds.size()), view, cx);
|
||||
cx.paint_layer(Some(layout.bounds), |cx| {
|
||||
element.paint(bounds.origin(), bounds, view, cx);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<ParentView: 'static, ChildView: View> IntoElement<ParentView> for ViewHandle<ChildView> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
87
crates/gpui3/Cargo.toml
Normal file
87
crates/gpui3/Cargo.toml
Normal file
@ -0,0 +1,87 @@
|
||||
[package]
|
||||
name = "gpui3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
description = "The next version of Zed's GPU-accelerated UI framework"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
|
||||
|
||||
[lib]
|
||||
path = "src/gpui3.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui_macros = { path = "../gpui_macros" }
|
||||
gpui3_macros = { path = "../gpui3_macros" }
|
||||
util = { path = "../util" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
async-task = "4.0.3"
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
ctor.workspace = true
|
||||
derive_more.workspace = true
|
||||
dhat = { version = "0.3", optional = true }
|
||||
env_logger = { version = "0.9", optional = true }
|
||||
etagere = "0.2"
|
||||
futures.workspace = true
|
||||
image = "0.23"
|
||||
itertools = "0.10"
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
num_cpus = "1.13"
|
||||
ordered-float.workspace = true
|
||||
parking = "2.0.0"
|
||||
parking_lot.workspace = true
|
||||
pathfinder_geometry = "0.5"
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
refineable.workspace = true
|
||||
resvg = "0.14"
|
||||
seahash = "4.1"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny-skia = "0.5"
|
||||
usvg = { version = "0.14", features = [] }
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
waker-fn = "1.1.0"
|
||||
slotmap = "1.0.6"
|
||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
||||
schemars.workspace = true
|
||||
plane-split = "0.18.0"
|
||||
bitflags = "2.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
dhat = "0.3"
|
||||
env_logger.workspace = true
|
||||
png = "0.16"
|
||||
simplelog = "0.9"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.65.1"
|
||||
cbindgen = "0.26.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
media = { path = "../media" }
|
||||
anyhow.workspace = true
|
||||
block = "0.1"
|
||||
cocoa = "0.24"
|
||||
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
|
||||
core-graphics = "0.22.3"
|
||||
core-text = "19.2"
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
|
||||
foreign-types = "0.3"
|
||||
log.workspace = true
|
||||
metal = "0.21.0"
|
||||
objc = "0.2"
|
115
crates/gpui3/build.rs
Normal file
115
crates/gpui3/build.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
process::{self, Command},
|
||||
};
|
||||
|
||||
use cbindgen::Config;
|
||||
|
||||
fn main() {
|
||||
generate_dispatch_bindings();
|
||||
let header_path = generate_shader_bindings();
|
||||
compile_metal_shaders(&header_path);
|
||||
}
|
||||
|
||||
fn generate_dispatch_bindings() {
|
||||
println!("cargo:rustc-link-lib=framework=System");
|
||||
println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("src/platform/mac/dispatch.h")
|
||||
.allowlist_var("_dispatch_main_q")
|
||||
.allowlist_function("dispatch_async_f")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||
.layout_tests(false)
|
||||
.generate()
|
||||
.expect("unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("dispatch_sys.rs"))
|
||||
.expect("couldn't write dispatch bindings");
|
||||
}
|
||||
|
||||
fn generate_shader_bindings() -> PathBuf {
|
||||
let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
|
||||
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let mut config = Config::default();
|
||||
config.include_guard = Some("SCENE_H".into());
|
||||
config.language = cbindgen::Language::C;
|
||||
config.export.include.extend([
|
||||
"Bounds".into(),
|
||||
"Corners".into(),
|
||||
"Edges".into(),
|
||||
"Size".into(),
|
||||
"Pixels".into(),
|
||||
"PointF".into(),
|
||||
"Hsla".into(),
|
||||
"Quad".into(),
|
||||
"QuadInputIndex".into(),
|
||||
"QuadUniforms".into(),
|
||||
]);
|
||||
config.no_includes = true;
|
||||
config.enumeration.prefix_with_name = true;
|
||||
cbindgen::Builder::new()
|
||||
.with_src(crate_dir.join("src/scene.rs"))
|
||||
.with_src(crate_dir.join("src/geometry.rs"))
|
||||
.with_src(crate_dir.join("src/color.rs"))
|
||||
.with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
|
||||
.with_config(config)
|
||||
.generate()
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file(&output_path);
|
||||
output_path
|
||||
}
|
||||
|
||||
fn compile_metal_shaders(header_path: &Path) {
|
||||
let shader_path = "./src/platform/mac/shaders.metal";
|
||||
let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
|
||||
let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
|
||||
|
||||
println!("cargo:rerun-if-changed={}", header_path.display());
|
||||
println!("cargo:rerun-if-changed={}", shader_path);
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args([
|
||||
"-sdk",
|
||||
"macosx",
|
||||
"metal",
|
||||
"-gline-tables-only",
|
||||
"-mmacosx-version-min=10.15.7",
|
||||
"-MO",
|
||||
"-c",
|
||||
shader_path,
|
||||
"-include",
|
||||
&header_path.to_str().unwrap(),
|
||||
"-o",
|
||||
])
|
||||
.arg(&air_output_path)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!(
|
||||
"metal shader compilation failed:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args(["-sdk", "macosx", "metallib"])
|
||||
.arg(air_output_path)
|
||||
.arg("-o")
|
||||
.arg(metallib_output_path)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!(
|
||||
"metallib compilation failed:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
343
crates/gpui3/src/app.rs
Normal file
343
crates/gpui3/src/app.rs
Normal file
@ -0,0 +1,343 @@
|
||||
mod async_context;
|
||||
mod entity_map;
|
||||
mod model_context;
|
||||
|
||||
pub use async_context::*;
|
||||
pub use entity_map::*;
|
||||
pub use model_context::*;
|
||||
use refineable::Refineable;
|
||||
|
||||
use crate::{
|
||||
current_platform, run_on_main, spawn_on_main, Context, LayoutId, MainThread, MainThreadOnly,
|
||||
Platform, PlatformDispatcher, RootView, TextStyle, TextStyleRefinement, TextSystem, Window,
|
||||
WindowContext, WindowHandle, WindowId,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, VecDeque};
|
||||
use futures::Future;
|
||||
use parking_lot::Mutex;
|
||||
use slotmap::SlotMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{type_name, Any, TypeId},
|
||||
mem,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct App(Arc<Mutex<MainThread<AppContext>>>);
|
||||
|
||||
impl App {
|
||||
pub fn production() -> Self {
|
||||
Self::new(current_platform())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub fn test() -> Self {
|
||||
Self::new(Arc::new(super::TestPlatform::new()))
|
||||
}
|
||||
|
||||
fn new(platform: Arc<dyn Platform>) -> Self {
|
||||
let dispatcher = platform.dispatcher();
|
||||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||
let entities = EntityMap::new();
|
||||
let unit_entity = entities.redeem(entities.reserve(), ());
|
||||
Self(Arc::new_cyclic(|this| {
|
||||
Mutex::new(MainThread::new(AppContext {
|
||||
this: this.clone(),
|
||||
platform: MainThreadOnly::new(platform, dispatcher.clone()),
|
||||
dispatcher,
|
||||
text_system,
|
||||
pending_updates: 0,
|
||||
text_style_stack: Vec::new(),
|
||||
state_stacks_by_type: HashMap::default(),
|
||||
unit_entity,
|
||||
entities,
|
||||
windows: SlotMap::with_key(),
|
||||
pending_effects: Default::default(),
|
||||
observers: Default::default(),
|
||||
layout_id_buffer: Default::default(),
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn run<F>(self, on_finish_launching: F)
|
||||
where
|
||||
F: 'static + FnOnce(&mut MainThread<AppContext>),
|
||||
{
|
||||
let this = self.clone();
|
||||
let platform = self.0.lock().platform.clone();
|
||||
platform.borrow_on_main_thread().run(Box::new(move || {
|
||||
let cx = &mut *this.0.lock();
|
||||
on_finish_launching(cx);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
|
||||
|
||||
pub struct AppContext {
|
||||
this: Weak<Mutex<MainThread<AppContext>>>,
|
||||
platform: MainThreadOnly<dyn Platform>,
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
text_system: Arc<TextSystem>,
|
||||
pending_updates: usize,
|
||||
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
|
||||
pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
|
||||
pub(crate) unit_entity: Handle<()>,
|
||||
pub(crate) entities: EntityMap,
|
||||
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
|
||||
pub(crate) pending_effects: VecDeque<Effect>,
|
||||
pub(crate) observers: HashMap<EntityId, Handlers>,
|
||||
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.pending_updates += 1;
|
||||
let result = update(self);
|
||||
if self.pending_updates == 1 {
|
||||
self.flush_effects();
|
||||
}
|
||||
self.pending_updates -= 1;
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn update_window<R>(
|
||||
&mut self,
|
||||
id: WindowId,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
self.update(|cx| {
|
||||
let mut window = cx
|
||||
.windows
|
||||
.get_mut(id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.take()
|
||||
.unwrap();
|
||||
|
||||
let result = update(&mut WindowContext::mutable(cx, &mut window));
|
||||
|
||||
cx.windows
|
||||
.get_mut(id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.replace(window);
|
||||
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
fn flush_effects(&mut self) {
|
||||
while let Some(effect) = self.pending_effects.pop_front() {
|
||||
match effect {
|
||||
Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
|
||||
}
|
||||
}
|
||||
|
||||
let dirty_window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.filter_map(|(window_id, window)| {
|
||||
let window = window.as_ref().unwrap();
|
||||
if window.dirty {
|
||||
Some(window_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for dirty_window_id in dirty_window_ids {
|
||||
self.update_window(dirty_window_id, |cx| cx.draw())
|
||||
.unwrap()
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_notify_effect(&mut self, updated_entity: EntityId) {
|
||||
if let Some(mut handlers) = self.observers.remove(&updated_entity) {
|
||||
handlers.retain(|handler| handler(self));
|
||||
if let Some(new_handlers) = self.observers.remove(&updated_entity) {
|
||||
handlers.extend(new_handlers);
|
||||
}
|
||||
self.observers.insert(updated_entity, handlers);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_async(&self) -> AsyncContext {
|
||||
AsyncContext(unsafe { mem::transmute(self.this.clone()) })
|
||||
}
|
||||
|
||||
pub fn run_on_main<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
|
||||
) -> impl Future<Output = R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
let this = self.this.upgrade().unwrap();
|
||||
run_on_main(self.dispatcher.clone(), move || {
|
||||
let cx = &mut *this.lock();
|
||||
cx.update(|cx| {
|
||||
f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spawn_on_main<F, R>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut MainThread<AppContext>) -> F + Send + 'static,
|
||||
) -> impl Future<Output = R>
|
||||
where
|
||||
F: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let this = self.this.upgrade().unwrap();
|
||||
spawn_on_main(self.dispatcher.clone(), move || {
|
||||
let cx = &mut *this.lock();
|
||||
cx.update(|cx| {
|
||||
f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn text_system(&self) -> &Arc<TextSystem> {
|
||||
&self.text_system
|
||||
}
|
||||
|
||||
pub fn text_style(&self) -> TextStyle {
|
||||
let mut style = TextStyle::default();
|
||||
for refinement in &self.text_style_stack {
|
||||
style.refine(refinement);
|
||||
}
|
||||
style
|
||||
}
|
||||
|
||||
pub fn state<S: 'static>(&self) -> &S {
|
||||
self.state_stacks_by_type
|
||||
.get(&TypeId::of::<S>())
|
||||
.and_then(|stack| stack.last())
|
||||
.and_then(|any_state| any_state.downcast_ref::<S>())
|
||||
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn state_mut<S: 'static>(&mut self) -> &mut S {
|
||||
self.state_stacks_by_type
|
||||
.get_mut(&TypeId::of::<S>())
|
||||
.and_then(|stack| stack.last_mut())
|
||||
.and_then(|any_state| any_state.downcast_mut::<S>())
|
||||
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
|
||||
self.text_style_stack.push(text_style);
|
||||
}
|
||||
|
||||
pub(crate) fn pop_text_style(&mut self) {
|
||||
self.text_style_stack.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn push_state<T: Send + Sync + 'static>(&mut self, state: T) {
|
||||
self.state_stacks_by_type
|
||||
.entry(TypeId::of::<T>())
|
||||
.or_default()
|
||||
.push(Box::new(state));
|
||||
}
|
||||
|
||||
pub(crate) fn pop_state<T: 'static>(&mut self) {
|
||||
self.state_stacks_by_type
|
||||
.get_mut(&TypeId::of::<T>())
|
||||
.and_then(|stack| stack.pop())
|
||||
.expect("state stack underflow");
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>;
|
||||
type Result<T> = T;
|
||||
|
||||
fn entity<T: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Handle<T> {
|
||||
let slot = self.entities.reserve();
|
||||
let entity = build_entity(&mut ModelContext::mutable(self, slot.id));
|
||||
self.entities.redeem(slot, entity)
|
||||
}
|
||||
|
||||
fn update_entity<T: Send + Sync + 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> R {
|
||||
let mut entity = self.entities.lease(handle);
|
||||
let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id));
|
||||
self.entities.end_lease(entity);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl MainThread<AppContext> {
|
||||
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.0.update(|cx| {
|
||||
update(unsafe {
|
||||
std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_window<R>(
|
||||
&mut self,
|
||||
id: WindowId,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
self.0.update_window(id, |cx| {
|
||||
update(unsafe {
|
||||
std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn platform(&self) -> &dyn Platform {
|
||||
self.platform.borrow_on_main_thread()
|
||||
}
|
||||
|
||||
pub fn activate(&mut self, ignoring_other_apps: bool) {
|
||||
self.platform().activate(ignoring_other_apps);
|
||||
}
|
||||
|
||||
pub fn open_window<S: 'static + Send + Sync>(
|
||||
&mut self,
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> RootView<S> + Send + 'static,
|
||||
) -> WindowHandle<S> {
|
||||
self.update(|cx| {
|
||||
let id = cx.windows.insert(None);
|
||||
let handle = WindowHandle::new(id);
|
||||
let mut window = Window::new(handle.into(), options, cx);
|
||||
let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
|
||||
window.root_view.replace(root_view.into_any());
|
||||
cx.windows.get_mut(id).unwrap().replace(window);
|
||||
handle
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Effect {
|
||||
Notify(EntityId),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AppContext;
|
||||
|
||||
#[test]
|
||||
fn test_app_context_send_sync() {
|
||||
// This will not compile if `AppContext` does not implement `Send`
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<AppContext>();
|
||||
}
|
||||
}
|
52
crates/gpui3/src/app/async_context.rs
Normal file
52
crates/gpui3/src/app/async_context.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use crate::{AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, WindowContext};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Weak;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncContext(pub(crate) Weak<Mutex<AppContext>>);
|
||||
|
||||
impl Context for AsyncContext {
|
||||
type EntityContext<'a, 'b, T: 'static + Send + Sync> = ModelContext<'a, T>;
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn entity<T: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Result<Handle<T>> {
|
||||
let app = self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock();
|
||||
Ok(lock.entity(build_entity))
|
||||
}
|
||||
|
||||
fn update_entity<T: Send + Sync + 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> Result<R> {
|
||||
let app = self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock();
|
||||
Ok(lock.update_entity(handle, update))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncContext {
|
||||
pub fn update_window<T>(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
update: impl FnOnce(&mut WindowContext) -> T + Send + Sync,
|
||||
) -> Result<T> {
|
||||
let app = self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app_context = app.lock();
|
||||
app_context.update_window(handle.id, update)
|
||||
}
|
||||
}
|
175
crates/gpui3/src/app/entity_map.rs
Normal file
175
crates/gpui3/src/app/entity_map.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use crate::Context;
|
||||
use anyhow::{anyhow, Result};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use slotmap::{SecondaryMap, SlotMap};
|
||||
use std::{
|
||||
any::Any,
|
||||
marker::PhantomData,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc, Weak,
|
||||
},
|
||||
};
|
||||
|
||||
slotmap::new_key_type! { pub struct EntityId; }
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Lease<T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
entity: Box<T>,
|
||||
pub id: EntityId,
|
||||
}
|
||||
|
||||
pub(crate) struct EntityMap {
|
||||
ref_counts: Arc<RwLock<RefCounts>>,
|
||||
entities: Arc<Mutex<SecondaryMap<EntityId, Box<dyn Any + Send + Sync>>>>,
|
||||
}
|
||||
|
||||
impl EntityMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ref_counts: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
entities: Arc::new(Mutex::new(SecondaryMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve<T: 'static + Send + Sync>(&self) -> Slot<T> {
|
||||
let id = self.ref_counts.write().insert(1.into());
|
||||
Slot(Handle::new(id, Arc::downgrade(&self.ref_counts)))
|
||||
}
|
||||
|
||||
pub fn redeem<T: 'static + Any + Send + Sync>(&self, slot: Slot<T>, entity: T) -> Handle<T> {
|
||||
let handle = slot.0;
|
||||
self.entities.lock().insert(handle.id, Box::new(entity));
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn lease<T: 'static + Send + Sync>(&self, handle: &Handle<T>) -> Lease<T> {
|
||||
let id = handle.id;
|
||||
let entity = self
|
||||
.entities
|
||||
.lock()
|
||||
.remove(id)
|
||||
.expect("Circular entity lease. Is the entity already being updated?")
|
||||
.downcast::<T>()
|
||||
.unwrap();
|
||||
Lease { id, entity }
|
||||
}
|
||||
|
||||
pub fn end_lease<T: 'static + Send + Sync>(&mut self, lease: Lease<T>) {
|
||||
self.entities.lock().insert(lease.id, lease.entity);
|
||||
}
|
||||
|
||||
pub fn weak_handle<T: 'static + Send + Sync>(&self, id: EntityId) -> WeakHandle<T> {
|
||||
WeakHandle {
|
||||
id,
|
||||
entity_type: PhantomData,
|
||||
ref_counts: Arc::downgrade(&self.ref_counts),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Slot<T: Send + Sync + 'static>(Handle<T>);
|
||||
|
||||
pub struct Handle<T: Send + Sync> {
|
||||
pub(crate) id: EntityId,
|
||||
entity_type: PhantomData<T>,
|
||||
ref_counts: Weak<RwLock<RefCounts>>,
|
||||
}
|
||||
|
||||
type RefCounts = SlotMap<EntityId, AtomicUsize>;
|
||||
|
||||
impl<T: 'static + Send + Sync> Handle<T> {
|
||||
pub fn new(id: EntityId, ref_counts: Weak<RwLock<RefCounts>>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
entity_type: PhantomData,
|
||||
ref_counts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downgrade(&self) -> WeakHandle<T> {
|
||||
WeakHandle {
|
||||
id: self.id,
|
||||
entity_type: self.entity_type,
|
||||
ref_counts: self.ref_counts.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the entity referenced by this handle with the given function.
|
||||
///
|
||||
/// The update function receives a context appropriate for its environment.
|
||||
/// When updating in an `AppContext`, it receives a `ModelContext`.
|
||||
/// When updating an a `WindowContext`, it receives a `ViewContext`.
|
||||
pub fn update<C: Context, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
|
||||
) -> C::Result<R> {
|
||||
cx.update_entity(self, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
entity_type: PhantomData,
|
||||
ref_counts: self.ref_counts.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> Drop for Handle<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ref_counts) = self.ref_counts.upgrade() {
|
||||
if let Some(count) = ref_counts.read().get(self.id) {
|
||||
let prev_count = count.fetch_sub(1, SeqCst);
|
||||
assert_ne!(prev_count, 0, "Detected over-release of a handle.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WeakHandle<T> {
|
||||
pub(crate) id: EntityId,
|
||||
pub(crate) entity_type: PhantomData<T>,
|
||||
pub(crate) ref_counts: Weak<RwLock<RefCounts>>,
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> WeakHandle<T> {
|
||||
pub fn upgrade(&self, _: &impl Context) -> Option<Handle<T>> {
|
||||
let ref_counts = self.ref_counts.upgrade()?;
|
||||
ref_counts.read().get(self.id).unwrap().fetch_add(1, SeqCst);
|
||||
Some(Handle {
|
||||
id: self.id,
|
||||
entity_type: self.entity_type,
|
||||
ref_counts: self.ref_counts.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the entity referenced by this handle with the given function if
|
||||
/// the referenced entity still exists. Returns an error if the entity has
|
||||
/// been released.
|
||||
///
|
||||
/// The update function receives a context appropriate for its environment.
|
||||
/// When updating in an `AppContext`, it receives a `ModelContext`.
|
||||
/// When updating an a `WindowContext`, it receives a `ViewContext`.
|
||||
pub fn update<C: Context, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
Result<C::Result<R>>: crate::Flatten<R>,
|
||||
{
|
||||
crate::Flatten::flatten(
|
||||
self.upgrade(cx)
|
||||
.ok_or_else(|| anyhow!("entity release"))
|
||||
.map(|this| cx.update_entity(&this, update)),
|
||||
)
|
||||
}
|
||||
}
|
87
crates/gpui3/src/app/model_context.rs
Normal file
87
crates/gpui3/src/app/model_context.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use crate::{AppContext, Context, Effect, EntityId, Handle, Reference, WeakHandle};
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
pub struct ModelContext<'a, T> {
|
||||
app: Reference<'a, AppContext>,
|
||||
entity_type: PhantomData<T>,
|
||||
entity_id: EntityId,
|
||||
}
|
||||
|
||||
impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
|
||||
pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self {
|
||||
Self {
|
||||
app: Reference::Mutable(app),
|
||||
entity_type: PhantomData,
|
||||
entity_id,
|
||||
}
|
||||
}
|
||||
|
||||
// todo!
|
||||
// fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
|
||||
// let mut entity = self
|
||||
// .app
|
||||
// .entities
|
||||
// .get_mut(self.entity_id)
|
||||
// .unwrap()
|
||||
// .take()
|
||||
// .unwrap();
|
||||
// let result = update(entity.downcast_mut::<T>().unwrap(), self);
|
||||
// self.app
|
||||
// .entities
|
||||
// .get_mut(self.entity_id)
|
||||
// .unwrap()
|
||||
// .replace(entity);
|
||||
// result
|
||||
// }
|
||||
|
||||
pub fn handle(&self) -> WeakHandle<T> {
|
||||
self.app.entities.weak_handle(self.entity_id)
|
||||
}
|
||||
|
||||
pub fn observe<E: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
handle: &Handle<E>,
|
||||
on_notify: impl Fn(&mut T, Handle<E>, &mut ModelContext<'_, T>) + Send + Sync + 'static,
|
||||
) {
|
||||
let this = self.handle();
|
||||
let handle = handle.downgrade();
|
||||
self.app
|
||||
.observers
|
||||
.entry(handle.id)
|
||||
.or_default()
|
||||
.push(Arc::new(move |cx| {
|
||||
if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
|
||||
this.update(cx, |this, cx| on_notify(this, handle, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
self.app
|
||||
.pending_effects
|
||||
.push_back(Effect::Notify(self.entity_id));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Context for ModelContext<'a, T> {
|
||||
type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>;
|
||||
type Result<U> = U;
|
||||
|
||||
fn entity<U: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U,
|
||||
) -> Handle<U> {
|
||||
self.app.entity(build_entity)
|
||||
}
|
||||
|
||||
fn update_entity<U: Send + Sync + 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<U>,
|
||||
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
|
||||
) -> R {
|
||||
self.app.update_entity(handle, update)
|
||||
}
|
||||
}
|
0
crates/gpui3/src/arc_cow.rs
Normal file
0
crates/gpui3/src/arc_cow.rs
Normal file
238
crates/gpui3/src/color.rs
Normal file
238
crates/gpui3/src/color.rs
Normal file
@ -0,0 +1,238 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||
use std::fmt;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
pub fn rgb(hex: u32) -> Rgba {
|
||||
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
||||
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
|
||||
let b = (hex & 0xFF) as f32 / 255.0;
|
||||
Rgba { r, g, b, a: 1.0 }.into()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct Rgba {
|
||||
pub r: f32,
|
||||
pub g: f32,
|
||||
pub b: f32,
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub fn blend(&self, other: Rgba) -> Self {
|
||||
if other.a >= 1.0 {
|
||||
return other;
|
||||
} else if other.a <= 0.0 {
|
||||
return *self;
|
||||
} else {
|
||||
return Rgba {
|
||||
r: (self.r * (1.0 - other.a)) + (other.r * other.a),
|
||||
g: (self.g * (1.0 - other.a)) + (other.g * other.a),
|
||||
b: (self.b * (1.0 - other.a)) + (other.b * other.a),
|
||||
a: self.a,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RgbaVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RgbaVisitor {
|
||||
type Value = Rgba;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
|
||||
if value.len() == 7 || value.len() == 9 {
|
||||
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
|
||||
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
|
||||
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
|
||||
let a = if value.len() == 9 {
|
||||
u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
Ok(Rgba { r, g, b, a })
|
||||
} else {
|
||||
Err(E::custom(
|
||||
"Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Rgba {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_str(RgbaVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for Rgba {
|
||||
fn from(color: Hsla) -> Self {
|
||||
let h = color.h;
|
||||
let s = color.s;
|
||||
let l = color.l;
|
||||
|
||||
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
|
||||
let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
|
||||
let m = l - c / 2.0;
|
||||
let cm = c + m;
|
||||
let xm = x + m;
|
||||
|
||||
let (r, g, b) = match (h * 6.0).floor() as i32 {
|
||||
0 | 6 => (cm, xm, m),
|
||||
1 => (xm, cm, m),
|
||||
2 => (m, cm, xm),
|
||||
3 => (m, xm, cm),
|
||||
4 => (xm, m, cm),
|
||||
_ => (cm, m, xm),
|
||||
};
|
||||
|
||||
Rgba {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for Rgba {
|
||||
type Error = ParseIntError;
|
||||
|
||||
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
|
||||
let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
|
||||
let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
|
||||
let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
|
||||
let a = if value.len() > 7 {
|
||||
u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
Ok(Rgba { r, g, b, a })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Hsla {
|
||||
pub h: f32,
|
||||
pub s: f32,
|
||||
pub l: f32,
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Eq for Hsla {}
|
||||
|
||||
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
||||
Hsla {
|
||||
h: h.clamp(0., 1.),
|
||||
s: s.clamp(0., 1.),
|
||||
l: l.clamp(0., 1.),
|
||||
a: a.clamp(0., 1.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn black() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
s: 0.,
|
||||
l: 0.,
|
||||
a: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
impl Hsla {
|
||||
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
self.a == 0.0
|
||||
}
|
||||
|
||||
/// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
|
||||
///
|
||||
/// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
|
||||
/// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
|
||||
/// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
|
||||
///
|
||||
/// Assumptions:
|
||||
/// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
|
||||
/// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
|
||||
/// - RGB color components are contained in the range [0, 1].
|
||||
/// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
|
||||
pub fn blend(self, other: Hsla) -> Hsla {
|
||||
let alpha = other.a;
|
||||
|
||||
if alpha >= 1.0 {
|
||||
return other;
|
||||
} else if alpha <= 0.0 {
|
||||
return self;
|
||||
} else {
|
||||
let converted_self = Rgba::from(self);
|
||||
let converted_other = Rgba::from(other);
|
||||
let blended_rgb = converted_self.blend(converted_other);
|
||||
return Hsla::from(blended_rgb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
|
||||
/// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
|
||||
pub fn fade_out(&mut self, factor: f32) {
|
||||
self.a *= 1.0 - factor.clamp(0., 1.);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for Hsla {
|
||||
fn from(color: Rgba) -> Self {
|
||||
let r = color.r;
|
||||
let g = color.g;
|
||||
let b = color.b;
|
||||
|
||||
let max = r.max(g.max(b));
|
||||
let min = r.min(g.min(b));
|
||||
let delta = max - min;
|
||||
|
||||
let l = (max + min) / 2.0;
|
||||
let s = if l == 0.0 || l == 1.0 {
|
||||
0.0
|
||||
} else if l < 0.5 {
|
||||
delta / (2.0 * l)
|
||||
} else {
|
||||
delta / (2.0 - 2.0 * l)
|
||||
};
|
||||
|
||||
let h = if delta == 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
((g - b) / delta).rem_euclid(6.0) / 6.0
|
||||
} else if max == g {
|
||||
((b - r) / delta + 2.0) / 6.0
|
||||
} else {
|
||||
((r - g) / delta + 4.0) / 6.0
|
||||
};
|
||||
|
||||
Hsla {
|
||||
h,
|
||||
s,
|
||||
l,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Hsla {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// First, deserialize it into Rgba
|
||||
let rgba = Rgba::deserialize(deserializer)?;
|
||||
|
||||
// Then, use the From<Rgba> for Hsla implementation to convert it
|
||||
Ok(Hsla::from(rgba))
|
||||
}
|
||||
}
|
166
crates/gpui3/src/element.rs
Normal file
166
crates/gpui3/src/element.rs
Normal file
@ -0,0 +1,166 @@
|
||||
use super::{Layout, LayoutId, Pixels, Point, Result, ViewContext};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
|
||||
pub trait Element: 'static {
|
||||
type State;
|
||||
type FrameState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)>;
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
state: &mut Self::State,
|
||||
frame_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait ParentElement<S> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]>;
|
||||
|
||||
fn child(mut self, child: impl IntoAnyElement<S>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.children_mut().push(child.into_any());
|
||||
self
|
||||
}
|
||||
|
||||
fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.children_mut()
|
||||
.extend(iter.into_iter().map(|item| item.into_any()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
trait ElementObject<S> {
|
||||
fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId>;
|
||||
fn paint(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
offset: Option<Point<Pixels>>,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
struct RenderedElement<E: Element> {
|
||||
element: E,
|
||||
phase: ElementRenderPhase<E::FrameState>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum ElementRenderPhase<S> {
|
||||
#[default]
|
||||
Rendered,
|
||||
LayoutRequested {
|
||||
layout_id: LayoutId,
|
||||
frame_state: S,
|
||||
},
|
||||
Painted {
|
||||
layout: Layout,
|
||||
frame_state: S,
|
||||
},
|
||||
}
|
||||
|
||||
/// Internal struct that wraps an element to store Layout and FrameState after the element is rendered.
|
||||
/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
|
||||
/// improved usability.
|
||||
impl<E: Element> RenderedElement<E> {
|
||||
fn new(element: E) -> Self {
|
||||
RenderedElement {
|
||||
element,
|
||||
phase: ElementRenderPhase::Rendered,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
|
||||
fn layout(&mut self, state: &mut E::State, cx: &mut ViewContext<E::State>) -> Result<LayoutId> {
|
||||
let (layout_id, frame_state) = self.element.layout(state, cx)?;
|
||||
self.phase = ElementRenderPhase::LayoutRequested {
|
||||
layout_id,
|
||||
frame_state,
|
||||
};
|
||||
Ok(layout_id)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
state: &mut E::State,
|
||||
offset: Option<Point<Pixels>>,
|
||||
cx: &mut ViewContext<E::State>,
|
||||
) -> Result<()> {
|
||||
self.phase = match std::mem::take(&mut self.phase) {
|
||||
ElementRenderPhase::Rendered => panic!("must call layout before paint"),
|
||||
|
||||
ElementRenderPhase::LayoutRequested {
|
||||
layout_id,
|
||||
mut frame_state,
|
||||
} => {
|
||||
let mut layout = cx.layout(layout_id)?.clone();
|
||||
offset.map(|offset| layout.bounds.origin += offset);
|
||||
self.element
|
||||
.paint(layout.clone(), state, &mut frame_state, cx)?;
|
||||
ElementRenderPhase::Painted {
|
||||
layout,
|
||||
frame_state,
|
||||
}
|
||||
}
|
||||
|
||||
ElementRenderPhase::Painted {
|
||||
layout,
|
||||
mut frame_state,
|
||||
} => {
|
||||
self.element
|
||||
.paint(layout.clone(), state, &mut frame_state, cx)?;
|
||||
ElementRenderPhase::Painted {
|
||||
layout,
|
||||
frame_state,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyElement<S>(Box<dyn ElementObject<S>>);
|
||||
|
||||
impl<S> AnyElement<S> {
|
||||
pub fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId> {
|
||||
self.0.layout(state, cx)
|
||||
}
|
||||
|
||||
pub fn paint(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
offset: Option<Point<Pixels>>,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()> {
|
||||
self.0.paint(state, offset, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoAnyElement<S> {
|
||||
fn into_any(self) -> AnyElement<S>;
|
||||
}
|
||||
|
||||
impl<E: Element> IntoAnyElement<E::State> for E {
|
||||
fn into_any(self) -> AnyElement<E::State> {
|
||||
AnyElement(Box::new(RenderedElement::new(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoAnyElement<S> for AnyElement<S> {
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
self
|
||||
}
|
||||
}
|
11
crates/gpui3/src/elements.rs
Normal file
11
crates/gpui3/src/elements.rs
Normal file
@ -0,0 +1,11 @@
|
||||
mod div;
|
||||
mod img;
|
||||
mod stateless;
|
||||
mod svg;
|
||||
mod text;
|
||||
|
||||
pub use div::*;
|
||||
pub use img::*;
|
||||
pub use stateless::*;
|
||||
pub use svg::*;
|
||||
pub use text::*;
|
297
crates/gpui3/src/elements/div.rs
Normal file
297
crates/gpui3/src/elements/div.rs
Normal file
@ -0,0 +1,297 @@
|
||||
use crate::{
|
||||
AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
|
||||
Refineable, RefinementCascade, Result, StackContext, Style, StyleHelpers, Styled, ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct Div<S: 'static> {
|
||||
styles: RefinementCascade<Style>,
|
||||
// handlers: InteractionHandlers<V>,
|
||||
children: SmallVec<[AnyElement<S>; 2]>,
|
||||
scroll_state: Option<ScrollState>,
|
||||
}
|
||||
|
||||
pub fn div<S>() -> Div<S> {
|
||||
Div {
|
||||
styles: Default::default(),
|
||||
// handlers: Default::default(),
|
||||
children: Default::default(),
|
||||
scroll_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> Element for Div<S> {
|
||||
type State = S;
|
||||
type FrameState = Vec<LayoutId>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
let style = self.computed_style();
|
||||
let child_layout_ids = if let Some(text_style) = style.text_style(cx) {
|
||||
cx.with_text_style(text_style.clone(), |cx| self.layout_children(view, cx))?
|
||||
} else {
|
||||
self.layout_children(view, cx)?
|
||||
};
|
||||
|
||||
Ok((
|
||||
cx.request_layout(style.into(), child_layout_ids.clone())?,
|
||||
child_layout_ids,
|
||||
))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
state: &mut S,
|
||||
child_layouts: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()> {
|
||||
let Layout { order, bounds } = layout;
|
||||
|
||||
let style = self.computed_style();
|
||||
style.paint(order, bounds, cx);
|
||||
let overflow = &style.overflow;
|
||||
// // todo!("support only one dimension being hidden")
|
||||
// if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
|
||||
// cx.scene().push_layer(Some(bounds));
|
||||
// pop_layer = true;
|
||||
// }
|
||||
if let Some(text_style) = style.text_style(cx) {
|
||||
cx.with_text_style(text_style.clone(), |cx| {
|
||||
self.paint_children(overflow, state, cx)
|
||||
})?;
|
||||
} else {
|
||||
self.paint_children(overflow, state, cx)?;
|
||||
}
|
||||
|
||||
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
|
||||
|
||||
// todo!("enable inspector")
|
||||
// if cx.is_inspector_enabled() {
|
||||
// self.paint_inspector(parent_origin, layout, cx);
|
||||
// }
|
||||
//
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Div<S> {
|
||||
pub fn overflow_hidden(mut self) -> Self {
|
||||
self.declared_style().overflow.x = Some(Overflow::Hidden);
|
||||
self.declared_style().overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_x(mut self) -> Self {
|
||||
self.declared_style().overflow.x = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_y(mut self) -> Self {
|
||||
self.declared_style().overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.x = Some(Overflow::Scroll);
|
||||
self.declared_style().overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.x = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
fn scroll_offset(&self, overflow: &Point<Overflow>) -> Point<Pixels> {
|
||||
let mut offset = Point::default();
|
||||
if overflow.y == Overflow::Scroll {
|
||||
offset.y = self.scroll_state.as_ref().unwrap().y();
|
||||
}
|
||||
if overflow.x == Overflow::Scroll {
|
||||
offset.x = self.scroll_state.as_ref().unwrap().x();
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
fn layout_children(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> Result<Vec<LayoutId>> {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.map(|child| child.layout(view, cx))
|
||||
.collect::<Result<Vec<LayoutId>>>()
|
||||
}
|
||||
|
||||
fn paint_children(
|
||||
&mut self,
|
||||
overflow: &Point<Overflow>,
|
||||
state: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()> {
|
||||
let scroll_offset = self.scroll_offset(overflow);
|
||||
for child in &mut self.children {
|
||||
child.paint(state, Some(scroll_offset), cx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_scroll(
|
||||
&mut self,
|
||||
_order: u32,
|
||||
bounds: Bounds<Pixels>,
|
||||
overflow: Point<Overflow>,
|
||||
child_layout_ids: &[LayoutId],
|
||||
cx: &mut ViewContext<S>,
|
||||
) {
|
||||
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
|
||||
let mut scroll_max = Point::default();
|
||||
for child_layout_id in child_layout_ids {
|
||||
if let Some(child_layout) = cx.layout(*child_layout_id).log_err() {
|
||||
scroll_max = scroll_max.max(&child_layout.bounds.lower_right());
|
||||
}
|
||||
}
|
||||
scroll_max -= bounds.size;
|
||||
|
||||
// todo!("handle scroll")
|
||||
// let scroll_state = self.scroll_state.as_ref().unwrap().clone();
|
||||
// cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
|
||||
// if bounds.contains_point(event.position) {
|
||||
// let scroll_delta = match event.delta {
|
||||
// ScrollDelta::Pixels(delta) => delta,
|
||||
// ScrollDelta::Lines(delta) => cx.text_style().font_size * delta,
|
||||
// };
|
||||
// if overflow.x == Overflow::Scroll {
|
||||
// scroll_state.set_x(
|
||||
// (scroll_state.x() - scroll_delta.x())
|
||||
// .max(px(0.))
|
||||
// .min(scroll_max.x),
|
||||
// );
|
||||
// }
|
||||
// if overflow.y == Overflow::Scroll {
|
||||
// scroll_state.set_y(
|
||||
// (scroll_state.y() - scroll_delta.y())
|
||||
// .max(px(0.))
|
||||
// .min(scroll_max.y),
|
||||
// );
|
||||
// }
|
||||
// cx.repaint();
|
||||
// } else {
|
||||
// cx.bubble_event();
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
// fn paint_inspector(
|
||||
// &self,
|
||||
// parent_origin: Point<Pixels>,
|
||||
// layout: &Layout,
|
||||
// cx: &mut ViewContext<V>,
|
||||
// ) {
|
||||
// let style = self.styles.merged();
|
||||
// let bounds = layout.bounds;
|
||||
|
||||
// let hovered = bounds.contains_point(cx.mouse_position());
|
||||
// if hovered {
|
||||
// let rem_size = cx.rem_size();
|
||||
// // cx.scene().push_quad(scene::Quad {
|
||||
// // bounds,
|
||||
// // background: Some(hsla(0., 0., 1., 0.05).into()),
|
||||
// // border: gpui::Border {
|
||||
// // color: hsla(0., 0., 1., 0.2).into(),
|
||||
// // top: 1.,
|
||||
// // right: 1.,
|
||||
// // bottom: 1.,
|
||||
// // left: 1.,
|
||||
// // },
|
||||
// // corner_radii: CornerRadii::default()
|
||||
// // .refined(&style.corner_radii)
|
||||
// // .to_gpui(bounds.size(), rem_size),
|
||||
// // })
|
||||
// }
|
||||
|
||||
// // let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
|
||||
// // cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
|
||||
// // if bounds.contains_point(event.position) {
|
||||
// // if event.is_down {
|
||||
// // pressed.set(true);
|
||||
// // } else if pressed.get() {
|
||||
// // pressed.set(false);
|
||||
// // eprintln!("clicked div {:?} {:#?}", bounds, style);
|
||||
// // }
|
||||
// // }
|
||||
// // });
|
||||
|
||||
// // let hovered = Cell::new(hovered);
|
||||
// // cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
|
||||
// // cx.bubble_event();
|
||||
// // let hovered_now = bounds.contains_point(event.position);
|
||||
// // if hovered.get() != hovered_now {
|
||||
// // hovered.set(hovered_now);
|
||||
// // cx.repaint();
|
||||
// // }
|
||||
// // });
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
||||
impl<V> Styled for Div<V> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
&mut self.styles
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
self.styles.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> StyleHelpers for Div<V> {}
|
||||
|
||||
// impl<V> Interactive<V> for Div<V> {
|
||||
// fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
// &mut self.handlers
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<V: 'static> ParentElement<V> for Div<V> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ScrollState(Arc<Mutex<Point<Pixels>>>);
|
||||
|
||||
impl ScrollState {
|
||||
pub fn x(&self) -> Pixels {
|
||||
self.0.lock().x
|
||||
}
|
||||
|
||||
pub fn set_x(&self, value: Pixels) {
|
||||
self.0.lock().x = value;
|
||||
}
|
||||
|
||||
pub fn y(&self) -> Pixels {
|
||||
self.0.lock().y
|
||||
}
|
||||
|
||||
pub fn set_y(&self, value: Pixels) {
|
||||
self.0.lock().y = value;
|
||||
}
|
||||
}
|
105
crates/gpui3/src/elements/hoverable.rs
Normal file
105
crates/gpui3/src/elements/hoverable.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::{
|
||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||
interactive::{InteractionHandlers, Interactive},
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
|
||||
use refineable::{CascadeSlot, Refineable, RefinementCascade};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
pub struct Hoverable<E: Styleable> {
|
||||
hovered: Rc<Cell<bool>>,
|
||||
cascade_slot: CascadeSlot,
|
||||
hovered_style: <E::Style as Refineable>::Refinement,
|
||||
child: E,
|
||||
}
|
||||
|
||||
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
|
||||
Hoverable {
|
||||
hovered: Rc::new(Cell::new(false)),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
hovered_style: Default::default(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable> Styleable for Hoverable<E> {
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
&mut self.hovered_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
|
||||
type PaintState = E::PaintState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(self.child.layout(view, cx)?)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
self.hovered.set(bounds.contains_point(cx.mouse_position()));
|
||||
|
||||
let slot = self.cascade_slot;
|
||||
let style = self.hovered.get().then_some(self.hovered_style.clone());
|
||||
self.style_cascade().set(slot, style);
|
||||
|
||||
let hovered = self.hovered.clone();
|
||||
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
|
||||
cx.bubble_event();
|
||||
if bounds.contains_point(cx.mouse_position()) != hovered.get() {
|
||||
cx.repaint();
|
||||
}
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(view, parent_origin, layout, paint_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
|
||||
|
||||
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
self.child.interaction_handlers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
103
crates/gpui3/src/elements/img.rs
Normal file
103
crates/gpui3/src/elements/img.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
|
||||
use refineable::RefinementCascade;
|
||||
use std::marker::PhantomData;
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
pub struct Img<S> {
|
||||
style: RefinementCascade<Style>,
|
||||
uri: Option<ArcCow<'static, str>>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
pub fn img<S>() -> Img<S> {
|
||||
Img {
|
||||
style: RefinementCascade::default(),
|
||||
uri: None,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Img<S> {
|
||||
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.uri = Some(uri.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for Img<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut crate::ViewContext<Self::State>,
|
||||
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
let layout_id = cx.request_layout(style, [])?;
|
||||
Ok((layout_id, ()))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
_: &mut Self::FrameState,
|
||||
cx: &mut crate::ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
let style = self.computed_style();
|
||||
let order = layout.order;
|
||||
let bounds = layout.bounds;
|
||||
|
||||
style.paint(order, bounds, cx);
|
||||
|
||||
// if let Some(uri) = &self.uri {
|
||||
// let image_future = cx.image_cache.get(uri.clone());
|
||||
// if let Some(data) = image_future
|
||||
// .clone()
|
||||
// .now_or_never()
|
||||
// .and_then(ResultExt::log_err)
|
||||
// {
|
||||
// let rem_size = cx.rem_size();
|
||||
// cx.scene().push_image(scene::Image {
|
||||
// bounds,
|
||||
// border: gpui::Border {
|
||||
// color: style.border_color.unwrap_or_default().into(),
|
||||
// top: style.border_widths.top.to_pixels(rem_size),
|
||||
// right: style.border_widths.right.to_pixels(rem_size),
|
||||
// bottom: style.border_widths.bottom.to_pixels(rem_size),
|
||||
// left: style.border_widths.left.to_pixels(rem_size),
|
||||
// },
|
||||
// corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
|
||||
// grayscale: false,
|
||||
// data,
|
||||
// })
|
||||
// } else {
|
||||
// cx.spawn(|this, mut cx| async move {
|
||||
// if image_future.await.log_err().is_some() {
|
||||
// this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
// }
|
||||
// })
|
||||
// .detach();
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Styled for Img<S> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StyleHelpers for Img<S> {}
|
108
crates/gpui3/src/elements/pressable.rs
Normal file
108
crates/gpui3/src/elements/pressable.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use crate::{
|
||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||
interactive::{InteractionHandlers, Interactive},
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
|
||||
use refineable::{CascadeSlot, Refineable, RefinementCascade};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
pub struct Pressable<E: Styleable> {
|
||||
pressed: Rc<Cell<bool>>,
|
||||
pressed_style: <E::Style as Refineable>::Refinement,
|
||||
cascade_slot: CascadeSlot,
|
||||
child: E,
|
||||
}
|
||||
|
||||
pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
|
||||
Pressable {
|
||||
pressed: Rc::new(Cell::new(false)),
|
||||
pressed_style: Default::default(),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable> Styleable for Pressable<E> {
|
||||
type Style = E::Style;
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
&mut self.pressed_style
|
||||
}
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
|
||||
type PaintState = E::PaintState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.child.layout(view, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let slot = self.cascade_slot;
|
||||
let style = self.pressed.get().then_some(self.pressed_style.clone());
|
||||
self.style_cascade().set(slot, style);
|
||||
|
||||
let pressed = self.pressed.clone();
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
|
||||
if event.is_down {
|
||||
if bounds.contains_point(event.position) {
|
||||
pressed.set(true);
|
||||
cx.repaint();
|
||||
}
|
||||
} else if pressed.get() {
|
||||
pressed.set(false);
|
||||
cx.repaint();
|
||||
}
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(view, parent_origin, layout, paint_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
|
||||
|
||||
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
self.child.interaction_handlers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
30
crates/gpui3/src/elements/stateless.rs
Normal file
30
crates/gpui3/src/elements/stateless.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::Element;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Stateless<E: Element<State = ()>, S> {
|
||||
element: E,
|
||||
parent_state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<E: Element<State = ()>, S: Send + Sync + 'static> Element for Stateless<E, S> {
|
||||
type State = S;
|
||||
type FrameState = E::FrameState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut crate::ViewContext<Self::State>,
|
||||
) -> anyhow::Result<(crate::LayoutId, Self::FrameState)> {
|
||||
cx.erase_state(|cx| self.element.layout(&mut (), cx))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: crate::Layout,
|
||||
_: &mut Self::State,
|
||||
frame_state: &mut Self::FrameState,
|
||||
cx: &mut crate::ViewContext<Self::State>,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx))
|
||||
}
|
||||
}
|
82
crates/gpui3/src/elements/svg.rs
Normal file
82
crates/gpui3/src/elements/svg.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
|
||||
use refineable::RefinementCascade;
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
pub struct Svg<S> {
|
||||
path: Option<Cow<'static, str>>,
|
||||
style: RefinementCascade<Style>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
pub fn svg<S>() -> Svg<S> {
|
||||
Svg {
|
||||
path: None,
|
||||
style: RefinementCascade::<Style>::default(),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Svg<S> {
|
||||
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for Svg<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut S,
|
||||
cx: &mut crate::ViewContext<S>,
|
||||
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
Ok((cx.request_layout(style, [])?, ()))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_layout: Layout,
|
||||
_: &mut Self::State,
|
||||
_: &mut Self::FrameState,
|
||||
_cx: &mut crate::ViewContext<S>,
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// todo!
|
||||
// let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||
// if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
||||
// if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
|
||||
// let icon = scene::Icon {
|
||||
// bounds: layout.bounds + parent_origin,
|
||||
// svg: svg_tree,
|
||||
// path: path.clone(),
|
||||
// color: Rgba::from(fill_color).into(),
|
||||
// };
|
||||
|
||||
// cx.scene().push_icon(icon);
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Styled for Svg<S> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StyleHelpers for Svg<S> {}
|
121
crates/gpui3/src/elements/text.rs
Normal file
121
crates/gpui3/src/elements/text.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use crate::{
|
||||
AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, Pixels, Result, Size, ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use util::{arc_cow::ArcCow, ResultExt};
|
||||
|
||||
impl<S: 'static> IntoAnyElement<S> for ArcCow<'static, str> {
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
Text {
|
||||
text: self,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> IntoAnyElement<V> for &'static str {
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
Text {
|
||||
text: ArcCow::from(self),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Text<S> {
|
||||
text: ArcCow<'static, str>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for Text<S> {
|
||||
type State = S;
|
||||
type FrameState = Arc<Mutex<Option<TextLayout>>>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
dbg!("layout text");
|
||||
|
||||
let text_system = cx.text_system().clone();
|
||||
let text_style = cx.text_style();
|
||||
let font_size = text_style.font_size * cx.rem_size();
|
||||
let line_height = text_style
|
||||
.line_height
|
||||
.to_pixels(font_size.into(), cx.rem_size());
|
||||
let text = self.text.clone();
|
||||
let paint_state = Arc::new(Mutex::new(None));
|
||||
|
||||
let rem_size = cx.rem_size();
|
||||
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
|
||||
let frame_state = paint_state.clone();
|
||||
move |_, _| {
|
||||
dbg!("starting measurement");
|
||||
let Some(line_layout) = text_system
|
||||
.layout_line(
|
||||
text.as_ref(),
|
||||
font_size,
|
||||
&[(text.len(), text_style.to_run())],
|
||||
)
|
||||
.log_err()
|
||||
else {
|
||||
return Size::default();
|
||||
};
|
||||
dbg!("bbbb");
|
||||
|
||||
let size = Size {
|
||||
width: line_layout.width(),
|
||||
height: line_height,
|
||||
};
|
||||
|
||||
frame_state.lock().replace(TextLayout {
|
||||
line: Arc::new(line_layout),
|
||||
line_height,
|
||||
});
|
||||
|
||||
dbg!(size)
|
||||
}
|
||||
});
|
||||
|
||||
dbg!("got to end of text layout");
|
||||
Ok((layout_id?, paint_state))
|
||||
}
|
||||
|
||||
fn paint<'a>(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
paint_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()> {
|
||||
let bounds = layout.bounds;
|
||||
|
||||
let line;
|
||||
let line_height;
|
||||
{
|
||||
let paint_state = paint_state.lock();
|
||||
let paint_state = paint_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
line = paint_state.line.clone();
|
||||
line_height = paint_state.line_height;
|
||||
}
|
||||
|
||||
let _text_style = cx.text_style();
|
||||
|
||||
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
|
||||
let visible_bounds = bounds;
|
||||
line.paint(bounds.origin, visible_bounds, line_height, cx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextLayout {
|
||||
line: Arc<Line>,
|
||||
line_height: Pixels,
|
||||
}
|
1095
crates/gpui3/src/executor.rs
Normal file
1095
crates/gpui3/src/executor.rs
Normal file
File diff suppressed because it is too large
Load Diff
723
crates/gpui3/src/geometry.rs
Normal file
723
crates/gpui3/src/geometry.rs
Normal file
@ -0,0 +1,723 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use core::fmt::Debug;
|
||||
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
|
||||
use refineable::Refineable;
|
||||
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign};
|
||||
|
||||
#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[refineable(debug)]
|
||||
#[repr(C)]
|
||||
pub struct Point<T: Clone + Debug> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
pub fn point<T: Clone + Debug>(x: T, y: T) -> Point<T> {
|
||||
Point { x, y }
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug> Point<T> {
|
||||
pub fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Point<U> {
|
||||
Point {
|
||||
x: f(self.x.clone()),
|
||||
y: f(self.y.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rhs> Mul<Rhs> for Point<T>
|
||||
where
|
||||
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
|
||||
Rhs: Clone + Debug,
|
||||
{
|
||||
type Output = Point<Rhs>;
|
||||
|
||||
fn mul(self, rhs: Rhs) -> Self::Output {
|
||||
Point {
|
||||
x: self.x * rhs.clone(),
|
||||
y: self.y * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Point<T> {
|
||||
fn mul_assign(&mut self, rhs: S) {
|
||||
self.x = self.x.clone() * rhs.clone();
|
||||
self.y = self.y.clone() * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Sub<Output = T>> SubAssign<Size<T>> for Point<T> {
|
||||
fn sub_assign(&mut self, rhs: Size<T>) {
|
||||
self.x = self.x.clone() - rhs.width;
|
||||
self.y = self.y.clone() - rhs.height;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Add<Output = T> + Copy> AddAssign<T> for Point<T> {
|
||||
fn add_assign(&mut self, rhs: T) {
|
||||
self.x = self.x.clone() + rhs;
|
||||
self.y = self.y.clone() + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Div<S, Output = T>, S: Clone> Div<S> for Point<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: S) -> Self::Output {
|
||||
Self {
|
||||
x: self.x / rhs.clone(),
|
||||
y: self.y / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + std::cmp::PartialOrd> Point<T> {
|
||||
pub fn max(&self, other: &Self) -> Self {
|
||||
Point {
|
||||
x: if self.x >= other.x {
|
||||
self.x.clone()
|
||||
} else {
|
||||
other.x.clone()
|
||||
},
|
||||
y: if self.y >= other.y {
|
||||
self.y.clone()
|
||||
} else {
|
||||
other.y.clone()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug> Clone for Point<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
x: self.x.clone(),
|
||||
y: self.y.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
|
||||
|
||||
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div)]
|
||||
#[refineable(debug)]
|
||||
#[repr(C)]
|
||||
pub struct Size<T: Clone + Debug> {
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Size<T> {}
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Size<T> {}
|
||||
|
||||
pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
|
||||
Size { width, height }
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug> Size<T> {
|
||||
pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Size<U> {
|
||||
Size {
|
||||
width: f(self.width.clone()),
|
||||
height: f(self.height.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rhs> Mul<Rhs> for Size<T>
|
||||
where
|
||||
T: Mul<Rhs, Output = Rhs> + Debug + Clone,
|
||||
Rhs: Debug + Clone,
|
||||
{
|
||||
type Output = Size<Rhs>;
|
||||
|
||||
fn mul(self, rhs: Rhs) -> Self::Output {
|
||||
Size {
|
||||
width: self.width * rhs.clone(),
|
||||
height: self.height * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
|
||||
fn mul_assign(&mut self, rhs: S) {
|
||||
self.width = self.width.clone() * rhs.clone();
|
||||
self.height = self.height.clone() * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
|
||||
fn from(val: Size<Option<Pixels>>) -> Self {
|
||||
Size {
|
||||
width: val.width.map(|p| p.0 as f32),
|
||||
height: val.height.map(|p| p.0 as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn full() -> Self {
|
||||
Self {
|
||||
width: relative(1.).into(),
|
||||
height: relative(1.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
width: px(0.).into(),
|
||||
height: px(0.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
width: Length::Auto,
|
||||
height: Length::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug, PartialEq)]
|
||||
#[refineable(debug)]
|
||||
#[repr(C)]
|
||||
pub struct Bounds<T: Clone + Debug> {
|
||||
pub origin: Point<T>,
|
||||
pub size: Size<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Bounds<T> {}
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
|
||||
|
||||
// Bounds<f32> * Pixels = Bounds<Pixels>
|
||||
impl<T, Rhs> Mul<Rhs> for Bounds<T>
|
||||
where
|
||||
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
|
||||
Rhs: Clone + Debug,
|
||||
{
|
||||
type Output = Bounds<Rhs>;
|
||||
|
||||
fn mul(self, rhs: Rhs) -> Self::Output {
|
||||
Bounds {
|
||||
origin: self.origin * rhs.clone(),
|
||||
size: self.size * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Bounds<T> {
|
||||
fn mul_assign(&mut self, rhs: S) {
|
||||
self.origin *= rhs.clone();
|
||||
self.size *= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Div<S, Output = T>, S: Clone> Div<S> for Bounds<T>
|
||||
where
|
||||
Size<T>: Div<S, Output = Size<T>>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: S) -> Self {
|
||||
Self {
|
||||
origin: self.origin / rhs.clone(),
|
||||
size: self.size / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Add<T, Output = T>> Bounds<T> {
|
||||
pub fn upper_right(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone() + self.size.width.clone(),
|
||||
y: self.origin.y.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lower_right(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone() + self.size.width.clone(),
|
||||
y: self.origin.y.clone() + self.size.height.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
|
||||
pub fn contains_point(&self, point: Point<T>) -> bool {
|
||||
point.x >= self.origin.x
|
||||
&& point.x <= self.origin.x.clone() + self.size.width.clone()
|
||||
&& point.y >= self.origin.y
|
||||
&& point.y <= self.origin.y.clone() + self.size.height.clone()
|
||||
}
|
||||
|
||||
pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Bounds<U> {
|
||||
Bounds {
|
||||
origin: self.origin.map(&f),
|
||||
size: self.size.map(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug)]
|
||||
#[refineable(debug)]
|
||||
#[repr(C)]
|
||||
pub struct Edges<T: Clone + Debug> {
|
||||
pub top: T,
|
||||
pub right: T,
|
||||
pub bottom: T,
|
||||
pub left: T,
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<Output = T>> Mul for Edges<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
top: self.top.clone() * rhs.top,
|
||||
right: self.right.clone() * rhs.right,
|
||||
bottom: self.bottom.clone() * rhs.bottom,
|
||||
left: self.left.clone() * rhs.left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Edges<T> {
|
||||
fn mul_assign(&mut self, rhs: S) {
|
||||
self.top = self.top.clone() * rhs.clone();
|
||||
self.right = self.right.clone() * rhs.clone();
|
||||
self.bottom = self.bottom.clone() * rhs.clone();
|
||||
self.left = self.left.clone() * rhs.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Copy> Copy for Edges<T> {}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Edges<T> {}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Edges<T> {}
|
||||
|
||||
impl<T: Clone + Debug> Edges<T> {
|
||||
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Edges<U> {
|
||||
Edges {
|
||||
top: f(&self.top),
|
||||
right: f(&self.right),
|
||||
bottom: f(&self.bottom),
|
||||
left: f(&self.left),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
|
||||
predicate(&self.top)
|
||||
|| predicate(&self.right)
|
||||
|| predicate(&self.bottom)
|
||||
|| predicate(&self.left)
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
top: Length::Auto,
|
||||
right: Length::Auto,
|
||||
bottom: Length::Auto,
|
||||
left: Length::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.).into(),
|
||||
right: px(0.).into(),
|
||||
bottom: px(0.).into(),
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.).into(),
|
||||
right: px(0.).into(),
|
||||
bottom: px(0.).into(),
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<AbsoluteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.).into(),
|
||||
right: px(0.).into(),
|
||||
bottom: px(0.).into(),
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
|
||||
Edges {
|
||||
top: self.top.to_pixels(rem_size),
|
||||
right: self.right.to_pixels(rem_size),
|
||||
bottom: self.bottom.to_pixels(rem_size),
|
||||
left: self.left.to_pixels(rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug)]
|
||||
#[refineable(debug)]
|
||||
#[repr(C)]
|
||||
pub struct Corners<T: Clone + Debug> {
|
||||
pub top_left: T,
|
||||
pub top_right: T,
|
||||
pub bottom_right: T,
|
||||
pub bottom_left: T,
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug> Corners<T> {
|
||||
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Corners<U> {
|
||||
Corners {
|
||||
top_left: f(&self.top_left),
|
||||
top_right: f(&self.top_right),
|
||||
bottom_right: f(&self.bottom_right),
|
||||
bottom_left: f(&self.bottom_left),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<Output = T>> Mul for Corners<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
top_left: self.top_left.clone() * rhs.top_left,
|
||||
top_right: self.top_right.clone() * rhs.top_right,
|
||||
bottom_right: self.bottom_right.clone() * rhs.bottom_right,
|
||||
bottom_left: self.bottom_left.clone() * rhs.bottom_left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Corners<T> {
|
||||
fn mul_assign(&mut self, rhs: S) {
|
||||
self.top_left = self.top_left.clone() * rhs.clone();
|
||||
self.top_right = self.top_right.clone() * rhs.clone();
|
||||
self.bottom_right = self.bottom_right.clone() * rhs.clone();
|
||||
self.bottom_left = self.bottom_left.clone() * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Copy> Copy for Corners<T> {}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Corners<T> {}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Corners<T> {}
|
||||
|
||||
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Pixels(pub(crate) f32);
|
||||
|
||||
impl Mul<f32> for Pixels {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, other: f32) -> Pixels {
|
||||
Pixels(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Pixels> for f32 {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, rhs: Pixels) -> Self::Output {
|
||||
Pixels(self * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixels {
|
||||
pub fn round(&self) -> Self {
|
||||
Self(self.0.round())
|
||||
}
|
||||
|
||||
pub fn to_device_pixels(&self, scale: f32) -> DevicePixels {
|
||||
DevicePixels((self.0 * scale).ceil() as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Pixels> for Pixels {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, rhs: Pixels) -> Self::Output {
|
||||
Pixels(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pixels {}
|
||||
|
||||
impl Ord for Pixels {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0.partial_cmp(&other.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Pixels {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Pixels {
|
||||
fn from(val: f64) -> Self {
|
||||
Pixels(val as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Pixels {
|
||||
fn from(val: f32) -> Self {
|
||||
Pixels(val)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for Pixels {}
|
||||
unsafe impl bytemuck::Zeroable for Pixels {}
|
||||
|
||||
impl Debug for Pixels {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} px", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for f32 {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
pixels.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Pixels> for f32 {
|
||||
fn from(pixels: &Pixels) -> Self {
|
||||
pixels.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for f64 {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
pixels.0 as f64
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct DevicePixels(pub(crate) u32);
|
||||
|
||||
unsafe impl bytemuck::Pod for DevicePixels {}
|
||||
unsafe impl bytemuck::Zeroable for DevicePixels {}
|
||||
|
||||
impl From<DevicePixels> for u32 {
|
||||
fn from(device_pixels: DevicePixels) -> Self {
|
||||
device_pixels.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for DevicePixels {
|
||||
fn from(val: u32) -> Self {
|
||||
DevicePixels(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
|
||||
pub struct Rems(f32);
|
||||
|
||||
impl Mul<Pixels> for Rems {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, other: Pixels) -> Pixels {
|
||||
Pixels(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Rems {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} rem", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum AbsoluteLength {
|
||||
Pixels(Pixels),
|
||||
Rems(Rems),
|
||||
}
|
||||
|
||||
impl AbsoluteLength {
|
||||
pub fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(px) => px.0 == 0.,
|
||||
AbsoluteLength::Rems(rems) => rems.0 == 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for AbsoluteLength {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
AbsoluteLength::Pixels(pixels)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rems> for AbsoluteLength {
|
||||
fn from(rems: Rems) -> Self {
|
||||
AbsoluteLength::Rems(rems)
|
||||
}
|
||||
}
|
||||
|
||||
impl AbsoluteLength {
|
||||
pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => *pixels,
|
||||
AbsoluteLength::Rems(rems) => *rems * rem_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AbsoluteLength {
|
||||
fn default() -> Self {
|
||||
px(0.).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DefiniteLength {
|
||||
Absolute(AbsoluteLength),
|
||||
/// A fraction of the parent's size between 0 and 1.
|
||||
Fraction(f32),
|
||||
}
|
||||
|
||||
impl DefiniteLength {
|
||||
pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
|
||||
match self {
|
||||
DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
|
||||
DefiniteLength::Fraction(fraction) => match base_size {
|
||||
AbsoluteLength::Pixels(px) => px * *fraction,
|
||||
AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DefiniteLength {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
|
||||
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for DefiniteLength {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
Self::Absolute(pixels.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rems> for DefiniteLength {
|
||||
fn from(rems: Rems) -> Self {
|
||||
Self::Absolute(rems.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteLength> for DefiniteLength {
|
||||
fn from(length: AbsoluteLength) -> Self {
|
||||
Self::Absolute(length)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefiniteLength {
|
||||
fn default() -> Self {
|
||||
Self::Absolute(AbsoluteLength::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A length that can be defined in pixels, rems, percent of parent, or auto.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Length {
|
||||
Definite(DefiniteLength),
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Debug for Length {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
|
||||
Length::Auto => write!(f, "auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relative(fraction: f32) -> DefiniteLength {
|
||||
DefiniteLength::Fraction(fraction).into()
|
||||
}
|
||||
|
||||
/// Returns the Golden Ratio, i.e. `~(1.0 + sqrt(5.0)) / 2.0`.
|
||||
pub fn phi() -> DefiniteLength {
|
||||
relative(1.61803398875)
|
||||
}
|
||||
|
||||
pub fn rems(rems: f32) -> Rems {
|
||||
Rems(rems)
|
||||
}
|
||||
|
||||
pub fn px(pixels: f32) -> Pixels {
|
||||
Pixels(pixels)
|
||||
}
|
||||
|
||||
pub fn auto() -> Length {
|
||||
Length::Auto
|
||||
}
|
||||
|
||||
impl From<Pixels> for Length {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
Self::Definite(pixels.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rems> for Length {
|
||||
fn from(rems: Rems) -> Self {
|
||||
Self::Definite(rems.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefiniteLength> for Length {
|
||||
fn from(length: DefiniteLength) -> Self {
|
||||
Self::Definite(length)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteLength> for Length {
|
||||
fn from(length: AbsoluteLength) -> Self {
|
||||
Self::Definite(length.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Length {
|
||||
fn default() -> Self {
|
||||
Self::Definite(DefiniteLength::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for Length {
|
||||
fn from(_: ()) -> Self {
|
||||
Self::Definite(DefiniteLength::default())
|
||||
}
|
||||
}
|
207
crates/gpui3/src/gpui3.rs
Normal file
207
crates/gpui3/src/gpui3.rs
Normal file
@ -0,0 +1,207 @@
|
||||
mod app;
|
||||
mod color;
|
||||
mod element;
|
||||
mod elements;
|
||||
mod executor;
|
||||
mod geometry;
|
||||
mod platform;
|
||||
mod scene;
|
||||
mod style;
|
||||
mod style_helpers;
|
||||
mod styled;
|
||||
mod taffy;
|
||||
mod text_system;
|
||||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
pub use anyhow::Result;
|
||||
pub use app::*;
|
||||
pub use color::*;
|
||||
pub use element::*;
|
||||
pub use elements::*;
|
||||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use gpui3_macros::*;
|
||||
pub use platform::*;
|
||||
pub use refineable::*;
|
||||
pub use scene::*;
|
||||
pub use serde;
|
||||
pub use serde_json;
|
||||
pub use smallvec;
|
||||
pub use smol::Timer;
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
pub use style::*;
|
||||
pub use style_helpers::*;
|
||||
pub use styled::*;
|
||||
use taffy::TaffyLayoutEngine;
|
||||
pub use taffy::{AvailableSpace, LayoutId};
|
||||
pub use text_system::*;
|
||||
pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
pub trait Context {
|
||||
type EntityContext<'a, 'w, T: 'static + Send + Sync>;
|
||||
type Result<T>;
|
||||
|
||||
fn entity<T: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Self::Result<Handle<T>>;
|
||||
|
||||
fn update_entity<T: Send + Sync + 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> Self::Result<R>;
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct MainThread<T>(T);
|
||||
|
||||
impl<T> MainThread<T> {
|
||||
fn new(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for MainThread<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for MainThread<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StackContext {
|
||||
fn app(&mut self) -> &mut AppContext;
|
||||
|
||||
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.app().push_text_style(style);
|
||||
let result = f(self);
|
||||
self.app().pop_text_style();
|
||||
result
|
||||
}
|
||||
|
||||
fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.app().push_state(state);
|
||||
let result = f(self);
|
||||
self.app().pop_state::<T>();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Flatten<T> {
|
||||
fn flatten(self) -> Result<T>;
|
||||
}
|
||||
|
||||
impl<T> Flatten<T> for Result<Result<T>> {
|
||||
fn flatten(self) -> Result<T> {
|
||||
self?
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Flatten<T> for Result<T> {
|
||||
fn flatten(self) -> Result<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
impl Default for SharedString {
|
||||
fn default() -> Self {
|
||||
Self(ArcCow::Owned("".into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for SharedString {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SharedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Reference<'a, T> {
|
||||
Immutable(&'a T),
|
||||
Mutable(&'a mut T),
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Reference<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Reference::Immutable(target) => target,
|
||||
Reference::Mutable(target) => target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for Reference<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self {
|
||||
Reference::Immutable(_) => {
|
||||
panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
|
||||
}
|
||||
Reference::Mutable(target) => target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MainThreadOnly<T: ?Sized> {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
value: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for MainThreadOnly<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
value: self.value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type
|
||||
/// to become `Send`.
|
||||
impl<T: 'static + ?Sized> MainThreadOnly<T> {
|
||||
pub(crate) fn new(value: Arc<T>, dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||
Self { dispatcher, value }
|
||||
}
|
||||
|
||||
pub(crate) fn borrow_on_main_thread(&self) -> &T {
|
||||
assert!(self.dispatcher.is_main_thread());
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized> Send for MainThreadOnly<T> {}
|
399
crates/gpui3/src/platform.rs
Normal file
399
crates/gpui3/src/platform.rs
Normal file
@ -0,0 +1,399 @@
|
||||
mod events;
|
||||
mod keystroke;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac;
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
mod test;
|
||||
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
|
||||
Scene, SharedString, Size,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot;
|
||||
use seahash::SeaHasher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ffi::c_void;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt::{self, Debug, Display},
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use events::*;
|
||||
pub use keystroke::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use mac::*;
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub use test::*;
|
||||
pub use time::UtcOffset;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn current_platform() -> Arc<dyn Platform> {
|
||||
Arc::new(MacPlatform::new())
|
||||
}
|
||||
|
||||
pub trait Platform: 'static {
|
||||
fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
|
||||
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 screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
|
||||
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
|
||||
fn main_window(&self) -> Option<AnyWindowHandle>;
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
|
||||
|
||||
fn open_url(&self, url: &str);
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
||||
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(Event) -> 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 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]) -> Result<()>;
|
||||
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
|
||||
fn delete_credentials(&self, url: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait PlatformScreen: Debug {
|
||||
fn id(&self) -> Option<ScreenId>;
|
||||
fn handle(&self) -> PlatformScreenHandle;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn bounds(&self) -> Bounds<Pixels>;
|
||||
fn content_bounds(&self) -> Bounds<Pixels>;
|
||||
}
|
||||
|
||||
pub struct PlatformScreenHandle(pub *mut c_void);
|
||||
|
||||
impl Debug for PlatformScreenHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "PlatformScreenHandle({:p})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for PlatformScreenHandle {}
|
||||
|
||||
pub trait PlatformWindow {
|
||||
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 screen(&self) -> Rc<dyn PlatformScreen>;
|
||||
fn mouse_position(&self) -> Point<Pixels>;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
|
||||
fn prompt(
|
||||
&self,
|
||||
level: WindowPromptLevel,
|
||||
msg: &str,
|
||||
answers: &[&str],
|
||||
) -> 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_event(&self, callback: Box<dyn FnMut(Event) -> 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);
|
||||
}
|
||||
|
||||
pub trait PlatformDispatcher: Send + Sync {
|
||||
fn is_main_thread(&self) -> bool;
|
||||
fn run_on_main_thread(&self, task: Runnable);
|
||||
}
|
||||
|
||||
pub trait PlatformTextSystem: Send + Sync {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
|
||||
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 rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_shift: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
options: RasterizationOptions,
|
||||
) -> Option<(Bounds<u32>, Vec<u8>)>;
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
width: Pixels,
|
||||
) -> Vec<usize>;
|
||||
}
|
||||
|
||||
pub trait InputHandler {
|
||||
fn selected_text_range(&self) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&self) -> Option<Range<usize>>;
|
||||
fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
);
|
||||
fn unmark_text(&mut self);
|
||||
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct ScreenId(pub(crate) Uuid);
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RasterizationOptions {
|
||||
Alpha,
|
||||
Bgra,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowOptions {
|
||||
pub bounds: WindowBounds,
|
||||
pub titlebar: Option<TitlebarOptions>,
|
||||
pub center: bool,
|
||||
pub focus: bool,
|
||||
pub show: bool,
|
||||
pub kind: WindowKind,
|
||||
pub is_movable: bool,
|
||||
pub screen: Option<PlatformScreenHandle>,
|
||||
}
|
||||
|
||||
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,
|
||||
screen: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TitlebarOptions {
|
||||
pub title: Option<SharedString>,
|
||||
pub appears_transparent: bool,
|
||||
pub traffic_light_position: Option<Point<Pixels>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Appearance {
|
||||
Light,
|
||||
VibrantLight,
|
||||
Dark,
|
||||
VibrantDark,
|
||||
}
|
||||
|
||||
impl Default for Appearance {
|
||||
fn default() -> Self {
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum WindowKind {
|
||||
Normal,
|
||||
PopUp,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub enum WindowBounds {
|
||||
Fullscreen,
|
||||
#[default]
|
||||
Maximized,
|
||||
Fixed(Bounds<Pixels>),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum WindowAppearance {
|
||||
Light,
|
||||
VibrantLight,
|
||||
Dark,
|
||||
VibrantDark,
|
||||
}
|
||||
|
||||
impl Default for WindowAppearance {
|
||||
fn default() -> Self {
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub enum WindowPromptLevel {
|
||||
#[default]
|
||||
Info,
|
||||
Warning,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathPromptOptions {
|
||||
pub files: bool,
|
||||
pub directories: bool,
|
||||
pub multiple: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum PromptLevel {
|
||||
Info,
|
||||
Warning,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CursorStyle {
|
||||
Arrow,
|
||||
ResizeLeftRight,
|
||||
ResizeUpDown,
|
||||
PointingHand,
|
||||
IBeam,
|
||||
}
|
||||
|
||||
impl Default for CursorStyle {
|
||||
fn default() -> Self {
|
||||
Self::Arrow
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SemanticVersion {
|
||||
major: usize,
|
||||
minor: usize,
|
||||
patch: usize,
|
||||
}
|
||||
|
||||
impl FromStr for SemanticVersion {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut components = s.trim().split('.');
|
||||
let major = components
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("missing major version number"))?
|
||||
.parse()?;
|
||||
let minor = components
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("missing minor version number"))?
|
||||
.parse()?;
|
||||
let patch = components
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("missing patch version number"))?
|
||||
.parse()?;
|
||||
Ok(Self {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SemanticVersion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ClipboardItem {
|
||||
pub(crate) text: String,
|
||||
pub(crate) metadata: Option<String>,
|
||||
}
|
||||
|
||||
impl ClipboardItem {
|
||||
pub fn new(text: String) -> Self {
|
||||
Self {
|
||||
text,
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
|
||||
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(&self) -> &String {
|
||||
&self.text
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
204
crates/gpui3/src/platform/events.rs
Normal file
204
crates/gpui3/src/platform/events.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use crate::{point, Keystroke, Modifiers, Pixels, Point};
|
||||
use std::{any::Any, ops::Deref};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct KeyDownEvent {
|
||||
pub keystroke: Keystroke,
|
||||
pub is_held: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyUpEvent {
|
||||
pub keystroke: Keystroke,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ModifiersChangedEvent {
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
impl Deref for ModifiersChangedEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.modifiers
|
||||
}
|
||||
}
|
||||
|
||||
/// The phase of a touch motion event.
|
||||
/// Based on the winit enum of the same name,
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TouchPhase {
|
||||
Started,
|
||||
Moved,
|
||||
Ended,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ScrollDelta {
|
||||
Pixels(Point<Pixels>),
|
||||
Lines(Point<f32>),
|
||||
}
|
||||
|
||||
impl Default for ScrollDelta {
|
||||
fn default() -> Self {
|
||||
Self::Lines(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollDelta {
|
||||
pub fn precise(&self) -> bool {
|
||||
match self {
|
||||
ScrollDelta::Pixels(_) => true,
|
||||
ScrollDelta::Lines(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
|
||||
match self {
|
||||
ScrollDelta::Pixels(delta) => *delta,
|
||||
ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScrollWheelEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub delta: ScrollDelta,
|
||||
pub modifiers: Modifiers,
|
||||
/// If the platform supports returning the phase of a scroll wheel event, it will be stored here
|
||||
pub phase: Option<TouchPhase>,
|
||||
}
|
||||
|
||||
impl Deref for ScrollWheelEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.modifiers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum NavigationDirection {
|
||||
Back,
|
||||
Forward,
|
||||
}
|
||||
|
||||
impl Default for NavigationDirection {
|
||||
fn default() -> Self {
|
||||
Self::Back
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Navigate(NavigationDirection),
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
MouseButton::Left,
|
||||
MouseButton::Right,
|
||||
MouseButton::Middle,
|
||||
MouseButton::Navigate(NavigationDirection::Back),
|
||||
MouseButton::Navigate(NavigationDirection::Forward),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MouseButton {
|
||||
fn default() -> Self {
|
||||
Self::Left
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseDownEvent {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUpEvent {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUp {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseMovedEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseExitedEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
impl Deref for MouseExitedEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.modifiers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
KeyDown(KeyDownEvent),
|
||||
KeyUp(KeyUpEvent),
|
||||
ModifiersChanged(ModifiersChangedEvent),
|
||||
MouseDown(MouseDownEvent),
|
||||
MouseUp(MouseUpEvent),
|
||||
MouseMoved(MouseMovedEvent),
|
||||
MouseExited(MouseExitedEvent),
|
||||
ScrollWheel(ScrollWheelEvent),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn position(&self) -> Option<Point<Pixels>> {
|
||||
match self {
|
||||
Event::KeyDown { .. } => None,
|
||||
Event::KeyUp { .. } => None,
|
||||
Event::ModifiersChanged { .. } => None,
|
||||
Event::MouseDown(event) => Some(event.position),
|
||||
Event::MouseUp(event) => Some(event.position),
|
||||
Event::MouseMoved(event) => Some(event.position),
|
||||
Event::MouseExited(event) => Some(event.position),
|
||||
Event::ScrollWheel(event) => Some(event.position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
|
||||
match self {
|
||||
Event::KeyDown { .. } => None,
|
||||
Event::KeyUp { .. } => None,
|
||||
Event::ModifiersChanged { .. } => None,
|
||||
Event::MouseDown(event) => Some(event),
|
||||
Event::MouseUp(event) => Some(event),
|
||||
Event::MouseMoved(event) => Some(event),
|
||||
Event::MouseExited(event) => Some(event),
|
||||
Event::ScrollWheel(event) => Some(event),
|
||||
}
|
||||
}
|
||||
}
|
121
crates/gpui3/src/platform/keystroke.rs
Normal file
121
crates/gpui3/src/platform/keystroke.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Modifiers {
|
||||
pub control: bool,
|
||||
pub alt: bool,
|
||||
pub shift: bool,
|
||||
pub command: bool,
|
||||
pub function: bool,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
pub fn modified(&self) -> bool {
|
||||
self.control || self.alt || self.shift || self.command || self.function
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
|
||||
pub struct Keystroke {
|
||||
pub key: String,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
impl Keystroke {
|
||||
pub fn parse(source: &str) -> anyhow::Result<Self> {
|
||||
let mut control = false;
|
||||
let mut alt = false;
|
||||
let mut shift = false;
|
||||
let mut command = false;
|
||||
let mut function = false;
|
||||
let mut key = None;
|
||||
|
||||
let mut components = source.split('-').peekable();
|
||||
while let Some(component) = components.next() {
|
||||
match component {
|
||||
"ctrl" => control = true,
|
||||
"alt" => alt = true,
|
||||
"shift" => shift = true,
|
||||
"cmd" => command = true,
|
||||
"fn" => function = true,
|
||||
_ => {
|
||||
if let Some(component) = components.peek() {
|
||||
if component.is_empty() && source.ends_with('-') {
|
||||
key = Some(String::from("-"));
|
||||
break;
|
||||
} else {
|
||||
return Err(anyhow!("Invalid keystroke `{}`", source));
|
||||
}
|
||||
} else {
|
||||
key = Some(String::from(component));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
|
||||
|
||||
Ok(Keystroke {
|
||||
modifiers: Modifiers {
|
||||
control,
|
||||
alt,
|
||||
shift,
|
||||
command,
|
||||
function,
|
||||
},
|
||||
key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn modified(&self) -> bool {
|
||||
self.modifiers.modified()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Keystroke {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Modifiers {
|
||||
control,
|
||||
alt,
|
||||
shift,
|
||||
command,
|
||||
function,
|
||||
} = self.modifiers;
|
||||
|
||||
if control {
|
||||
f.write_char('^')?;
|
||||
}
|
||||
if alt {
|
||||
f.write_char('⎇')?;
|
||||
}
|
||||
if command {
|
||||
f.write_char('⌘')?;
|
||||
}
|
||||
if shift {
|
||||
f.write_char('⇧')?;
|
||||
}
|
||||
if function {
|
||||
f.write_char('𝙛')?;
|
||||
}
|
||||
|
||||
let key = match self.key.as_str() {
|
||||
"backspace" => '⌫',
|
||||
"up" => '↑',
|
||||
"down" => '↓',
|
||||
"left" => '←',
|
||||
"right" => '→',
|
||||
"tab" => '⇥',
|
||||
"escape" => '⎋',
|
||||
key => {
|
||||
if key.len() == 1 {
|
||||
key.chars().next().unwrap().to_ascii_uppercase()
|
||||
} else {
|
||||
return f.write_str(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
f.write_char(key)
|
||||
}
|
||||
}
|
151
crates/gpui3/src/platform/mac.rs
Normal file
151
crates/gpui3/src/platform/mac.rs
Normal file
@ -0,0 +1,151 @@
|
||||
///! Macos screen have a y axis that goings up from the bottom of the screen and
|
||||
///! an origin at the bottom left of the main display.
|
||||
mod dispatcher;
|
||||
mod events;
|
||||
mod metal_renderer;
|
||||
mod open_type;
|
||||
mod platform;
|
||||
mod screen;
|
||||
mod text_system;
|
||||
mod window;
|
||||
mod window_appearence;
|
||||
|
||||
use crate::{px, size, Pixels, Size};
|
||||
use anyhow::anyhow;
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger, NSURL},
|
||||
};
|
||||
use metal_renderer::*;
|
||||
use objc::{
|
||||
msg_send,
|
||||
runtime::{BOOL, NO, YES},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use std::{
|
||||
ffi::{c_char, CStr, OsStr},
|
||||
ops::Range,
|
||||
os::unix::prelude::OsStrExt,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub use dispatcher::*;
|
||||
pub use platform::*;
|
||||
pub use screen::*;
|
||||
pub use text_system::*;
|
||||
pub use window::*;
|
||||
|
||||
trait BoolExt {
|
||||
fn to_objc(self) -> BOOL;
|
||||
}
|
||||
|
||||
impl BoolExt for bool {
|
||||
fn to_objc(self) -> BOOL {
|
||||
if self {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct NSRange {
|
||||
pub location: NSUInteger,
|
||||
pub length: NSUInteger,
|
||||
}
|
||||
|
||||
impl NSRange {
|
||||
fn invalid() -> Self {
|
||||
Self {
|
||||
location: NSNotFound as NSUInteger,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
self.location != NSNotFound as NSUInteger
|
||||
}
|
||||
|
||||
fn to_range(self) -> Option<Range<usize>> {
|
||||
if self.is_valid() {
|
||||
let start = self.location as usize;
|
||||
let end = start + self.length as usize;
|
||||
Some(start..end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<usize>> for NSRange {
|
||||
fn from(range: Range<usize>) -> Self {
|
||||
NSRange {
|
||||
location: range.start as NSUInteger,
|
||||
length: range.len() as NSUInteger,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl objc::Encode for NSRange {
|
||||
fn encode() -> objc::Encoding {
|
||||
let encoding = format!(
|
||||
"{{NSRange={}{}}}",
|
||||
NSUInteger::encode().as_str(),
|
||||
NSUInteger::encode().as_str()
|
||||
);
|
||||
unsafe { objc::Encoding::from_str(&encoding) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn ns_string(string: &str) -> id {
|
||||
NSString::alloc(nil).init_str(string).autorelease()
|
||||
}
|
||||
|
||||
impl From<NSSize> for Size<Pixels> {
|
||||
fn from(value: NSSize) -> Self {
|
||||
Size {
|
||||
width: px(value.width as f32),
|
||||
height: px(value.height as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NSRectExt {
|
||||
fn size(&self) -> Size<Pixels>;
|
||||
fn intersects(&self, other: Self) -> bool;
|
||||
}
|
||||
|
||||
impl NSRectExt for NSRect {
|
||||
fn size(&self) -> Size<Pixels> {
|
||||
size(px(self.size.width as f32), px(self.size.height as f32))
|
||||
}
|
||||
|
||||
fn intersects(&self, other: Self) -> bool {
|
||||
self.size.width > 0.
|
||||
&& self.size.height > 0.
|
||||
&& other.size.width > 0.
|
||||
&& other.size.height > 0.
|
||||
&& self.origin.x <= other.origin.x + other.size.width
|
||||
&& self.origin.x + self.size.width >= other.origin.x
|
||||
&& self.origin.y <= other.origin.y + other.size.height
|
||||
&& self.origin.y + self.size.height >= other.origin.y
|
||||
}
|
||||
}
|
||||
|
||||
// todo!
|
||||
#[allow(unused)]
|
||||
unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
|
||||
let path: *mut c_char = msg_send![url, fileSystemRepresentation];
|
||||
if path.is_null() {
|
||||
Err(anyhow!(
|
||||
"url is not a file path: {}",
|
||||
CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
|
||||
))
|
||||
} else {
|
||||
Ok(PathBuf::from(OsStr::from_bytes(
|
||||
CStr::from_ptr(path).to_bytes(),
|
||||
)))
|
||||
}
|
||||
}
|
1
crates/gpui3/src/platform/mac/dispatch.h
Normal file
1
crates/gpui3/src/platform/mac/dispatch.h
Normal file
@ -0,0 +1 @@
|
||||
#include <dispatch/dispatch.h>
|
42
crates/gpui3/src/platform/mac/dispatcher.rs
Normal file
42
crates/gpui3/src/platform/mac/dispatcher.rs
Normal file
@ -0,0 +1,42 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::PlatformDispatcher;
|
||||
use async_task::Runnable;
|
||||
use objc::{
|
||||
class, msg_send,
|
||||
runtime::{BOOL, YES},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use std::ffi::c_void;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
|
||||
|
||||
pub fn dispatch_get_main_queue() -> dispatch_queue_t {
|
||||
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
|
||||
}
|
||||
|
||||
pub struct MacDispatcher;
|
||||
|
||||
impl PlatformDispatcher for MacDispatcher {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
|
||||
is_main_thread == YES
|
||||
}
|
||||
|
||||
fn run_on_main_thread(&self, runnable: Runnable) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
runnable.into_raw() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
|
||||
extern "C" fn trampoline(runnable: *mut c_void) {
|
||||
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
}
|
356
crates/gpui3/src/platform/mac/events.rs
Normal file
356
crates/gpui3/src/platform/mac/events.rs
Normal file
@ -0,0 +1,356 @@
|
||||
use crate::{
|
||||
point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
|
||||
MouseButton, MouseDownEvent, MouseExitedEvent, MouseMovedEvent, MouseUpEvent,
|
||||
NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
|
||||
base::{id, YES},
|
||||
foundation::NSString as _,
|
||||
};
|
||||
use core_graphics::{
|
||||
event::{CGEvent, CGEventFlags, CGKeyCode},
|
||||
event_source::{CGEventSource, CGEventSourceStateID},
|
||||
};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignType;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
|
||||
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const SPACE_KEY: u16 = b' ' as u16;
|
||||
const ENTER_KEY: u16 = 0x0d;
|
||||
const NUMPAD_ENTER_KEY: u16 = 0x03;
|
||||
const ESCAPE_KEY: u16 = 0x1b;
|
||||
const TAB_KEY: u16 = 0x09;
|
||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||
|
||||
static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
|
||||
|
||||
#[ctor]
|
||||
unsafe fn build_event_source() {
|
||||
let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
|
||||
EVENT_SOURCE = source.as_ptr();
|
||||
mem::forget(source);
|
||||
}
|
||||
|
||||
// todo!
|
||||
#[allow(unused)]
|
||||
pub fn key_to_native(key: &str) -> Cow<str> {
|
||||
use cocoa::appkit::*;
|
||||
let code = match key {
|
||||
"space" => SPACE_KEY,
|
||||
"backspace" => BACKSPACE_KEY,
|
||||
"up" => NSUpArrowFunctionKey,
|
||||
"down" => NSDownArrowFunctionKey,
|
||||
"left" => NSLeftArrowFunctionKey,
|
||||
"right" => NSRightArrowFunctionKey,
|
||||
"pageup" => NSPageUpFunctionKey,
|
||||
"pagedown" => NSPageDownFunctionKey,
|
||||
"home" => NSHomeFunctionKey,
|
||||
"end" => NSEndFunctionKey,
|
||||
"delete" => NSDeleteFunctionKey,
|
||||
"f1" => NSF1FunctionKey,
|
||||
"f2" => NSF2FunctionKey,
|
||||
"f3" => NSF3FunctionKey,
|
||||
"f4" => NSF4FunctionKey,
|
||||
"f5" => NSF5FunctionKey,
|
||||
"f6" => NSF6FunctionKey,
|
||||
"f7" => NSF7FunctionKey,
|
||||
"f8" => NSF8FunctionKey,
|
||||
"f9" => NSF9FunctionKey,
|
||||
"f10" => NSF10FunctionKey,
|
||||
"f11" => NSF11FunctionKey,
|
||||
"f12" => NSF12FunctionKey,
|
||||
_ => return Cow::Borrowed(key),
|
||||
};
|
||||
Cow::Owned(String::from_utf16(&[code]).unwrap())
|
||||
}
|
||||
|
||||
unsafe fn read_modifiers(native_event: id) -> Modifiers {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||
|
||||
Modifiers {
|
||||
control,
|
||||
alt,
|
||||
shift,
|
||||
command,
|
||||
function,
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
|
||||
let event_type = native_event.eventType();
|
||||
|
||||
// Filter out event types that aren't in the NSEventType enum.
|
||||
// See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
|
||||
match event_type as u64 {
|
||||
0 | 21 | 32 | 33 | 35 | 36 | 37 => {
|
||||
return None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match event_type {
|
||||
NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
|
||||
modifiers: read_modifiers(native_event),
|
||||
})),
|
||||
NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
|
||||
keystroke: parse_keystroke(native_event),
|
||||
is_held: native_event.isARepeat() == YES,
|
||||
})),
|
||||
NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
|
||||
keystroke: parse_keystroke(native_event),
|
||||
})),
|
||||
NSEventType::NSLeftMouseDown
|
||||
| NSEventType::NSRightMouseDown
|
||||
| NSEventType::NSOtherMouseDown => {
|
||||
let button = match native_event.buttonNumber() {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Right,
|
||||
2 => MouseButton::Middle,
|
||||
3 => MouseButton::Navigate(NavigationDirection::Back),
|
||||
4 => MouseButton::Navigate(NavigationDirection::Forward),
|
||||
// Other mouse buttons aren't tracked currently
|
||||
_ => return None,
|
||||
};
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseDown(MouseDownEvent {
|
||||
button,
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
// MacOS screen coordinates are relative to bottom left
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
click_count: native_event.clickCount() as usize,
|
||||
})
|
||||
})
|
||||
}
|
||||
NSEventType::NSLeftMouseUp
|
||||
| NSEventType::NSRightMouseUp
|
||||
| NSEventType::NSOtherMouseUp => {
|
||||
let button = match native_event.buttonNumber() {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Right,
|
||||
2 => MouseButton::Middle,
|
||||
3 => MouseButton::Navigate(NavigationDirection::Back),
|
||||
4 => MouseButton::Navigate(NavigationDirection::Forward),
|
||||
// Other mouse buttons aren't tracked currently
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseUp(MouseUpEvent {
|
||||
button,
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
click_count: native_event.clickCount() as usize,
|
||||
})
|
||||
})
|
||||
}
|
||||
NSEventType::NSScrollWheel => window_height.map(|window_height| {
|
||||
let phase = match native_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
Some(TouchPhase::Started)
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
|
||||
_ => Some(TouchPhase::Moved),
|
||||
};
|
||||
|
||||
let raw_data = point(
|
||||
native_event.scrollingDeltaX() as f32,
|
||||
native_event.scrollingDeltaY() as f32,
|
||||
);
|
||||
|
||||
let delta = if native_event.hasPreciseScrollingDeltas() == YES {
|
||||
ScrollDelta::Pixels(raw_data.map(px))
|
||||
} else {
|
||||
ScrollDelta::Lines(raw_data)
|
||||
};
|
||||
|
||||
Self::ScrollWheel(ScrollWheelEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
delta,
|
||||
phase,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
}),
|
||||
NSEventType::NSLeftMouseDragged
|
||||
| NSEventType::NSRightMouseDragged
|
||||
| NSEventType::NSOtherMouseDragged => {
|
||||
let pressed_button = match native_event.buttonNumber() {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Right,
|
||||
2 => MouseButton::Middle,
|
||||
3 => MouseButton::Navigate(NavigationDirection::Back),
|
||||
4 => MouseButton::Navigate(NavigationDirection::Forward),
|
||||
// Other mouse buttons aren't tracked currently
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseMoved(MouseMovedEvent {
|
||||
pressed_button: Some(pressed_button),
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
})
|
||||
}
|
||||
NSEventType::NSMouseMoved => window_height.map(|window_height| {
|
||||
Self::MouseMoved(MouseMovedEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
pressed_button: None,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
}),
|
||||
NSEventType::NSMouseExited => window_height.map(|window_height| {
|
||||
Self::MouseExited(MouseExitedEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
|
||||
pressed_button: None,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
use cocoa::appkit::*;
|
||||
|
||||
let mut chars_ignoring_modifiers =
|
||||
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
|
||||
let modifiers = native_event.modifierFlags();
|
||||
|
||||
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
|
||||
&& first_char.map_or(true, |ch| {
|
||||
!(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
|
||||
});
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
let key = match first_char {
|
||||
Some(SPACE_KEY) => "space".to_string(),
|
||||
Some(BACKSPACE_KEY) => "backspace".to_string(),
|
||||
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
|
||||
Some(ESCAPE_KEY) => "escape".to_string(),
|
||||
Some(TAB_KEY) => "tab".to_string(),
|
||||
Some(SHIFT_TAB_KEY) => "tab".to_string(),
|
||||
Some(NSUpArrowFunctionKey) => "up".to_string(),
|
||||
Some(NSDownArrowFunctionKey) => "down".to_string(),
|
||||
Some(NSLeftArrowFunctionKey) => "left".to_string(),
|
||||
Some(NSRightArrowFunctionKey) => "right".to_string(),
|
||||
Some(NSPageUpFunctionKey) => "pageup".to_string(),
|
||||
Some(NSPageDownFunctionKey) => "pagedown".to_string(),
|
||||
Some(NSHomeFunctionKey) => "home".to_string(),
|
||||
Some(NSEndFunctionKey) => "end".to_string(),
|
||||
Some(NSDeleteFunctionKey) => "delete".to_string(),
|
||||
Some(NSF1FunctionKey) => "f1".to_string(),
|
||||
Some(NSF2FunctionKey) => "f2".to_string(),
|
||||
Some(NSF3FunctionKey) => "f3".to_string(),
|
||||
Some(NSF4FunctionKey) => "f4".to_string(),
|
||||
Some(NSF5FunctionKey) => "f5".to_string(),
|
||||
Some(NSF6FunctionKey) => "f6".to_string(),
|
||||
Some(NSF7FunctionKey) => "f7".to_string(),
|
||||
Some(NSF8FunctionKey) => "f8".to_string(),
|
||||
Some(NSF9FunctionKey) => "f9".to_string(),
|
||||
Some(NSF10FunctionKey) => "f10".to_string(),
|
||||
Some(NSF11FunctionKey) => "f11".to_string(),
|
||||
Some(NSF12FunctionKey) => "f12".to_string(),
|
||||
_ => {
|
||||
let mut chars_ignoring_modifiers_and_shift =
|
||||
chars_for_modified_key(native_event.keyCode(), false, false);
|
||||
|
||||
// Honor ⌘ when Dvorak-QWERTY is used.
|
||||
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
|
||||
if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
|
||||
chars_ignoring_modifiers =
|
||||
chars_for_modified_key(native_event.keyCode(), true, shift);
|
||||
chars_ignoring_modifiers_and_shift = chars_with_cmd;
|
||||
}
|
||||
|
||||
if shift {
|
||||
if chars_ignoring_modifiers_and_shift
|
||||
== chars_ignoring_modifiers.to_ascii_lowercase()
|
||||
{
|
||||
chars_ignoring_modifiers_and_shift
|
||||
} else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
|
||||
shift = false;
|
||||
chars_ignoring_modifiers
|
||||
} else {
|
||||
chars_ignoring_modifiers
|
||||
}
|
||||
} else {
|
||||
chars_ignoring_modifiers
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Keystroke {
|
||||
modifiers: Modifiers {
|
||||
control,
|
||||
alt,
|
||||
shift,
|
||||
command,
|
||||
function,
|
||||
},
|
||||
key,
|
||||
}
|
||||
}
|
||||
|
||||
fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
|
||||
// Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
|
||||
// always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
|
||||
// an event with the given flags instead lets us access `characters`, which always
|
||||
// returns a valid string.
|
||||
let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
|
||||
let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
|
||||
mem::forget(source);
|
||||
|
||||
let mut flags = CGEventFlags::empty();
|
||||
if cmd {
|
||||
flags |= CGEventFlags::CGEventFlagCommand;
|
||||
}
|
||||
if shift {
|
||||
flags |= CGEventFlags::CGEventFlagShift;
|
||||
}
|
||||
event.set_flags(flags);
|
||||
|
||||
unsafe {
|
||||
let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
|
||||
CStr::from_ptr(event.characters().UTF8String())
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
}
|
294
crates/gpui3/src/platform/mac/metal_renderer.rs
Normal file
294
crates/gpui3/src/platform/mac/metal_renderer.rs
Normal file
@ -0,0 +1,294 @@
|
||||
use crate::{point, size, DevicePixels, Quad, Scene, Size};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use cocoa::{
|
||||
base::{NO, YES},
|
||||
foundation::NSUInteger,
|
||||
quartzcore::AutoresizingMask,
|
||||
};
|
||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use std::{ffi::c_void, mem, ptr};
|
||||
|
||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||
const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
|
||||
|
||||
pub struct MetalRenderer {
|
||||
device: metal::Device,
|
||||
layer: metal::MetalLayer,
|
||||
command_queue: CommandQueue,
|
||||
quad_pipeline_state: metal::RenderPipelineState,
|
||||
unit_vertices: metal::Buffer,
|
||||
instances: metal::Buffer,
|
||||
}
|
||||
|
||||
impl MetalRenderer {
|
||||
pub fn new(is_opaque: bool) -> Self {
|
||||
const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
|
||||
|
||||
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
|
||||
device
|
||||
} else {
|
||||
log::error!("unable to access a compatible graphics device");
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
let layer = metal::MetalLayer::new();
|
||||
layer.set_device(&device);
|
||||
layer.set_pixel_format(PIXEL_FORMAT);
|
||||
layer.set_presents_with_transaction(true);
|
||||
layer.set_opaque(is_opaque);
|
||||
unsafe {
|
||||
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
|
||||
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
|
||||
let _: () = msg_send![
|
||||
&*layer,
|
||||
setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
|
||||
| AutoresizingMask::HEIGHT_SIZABLE
|
||||
];
|
||||
}
|
||||
|
||||
let library = device
|
||||
.new_library_with_data(SHADERS_METALLIB)
|
||||
.expect("error building metal library");
|
||||
|
||||
fn to_float2_bits(point: crate::PointF) -> u64 {
|
||||
unsafe {
|
||||
let mut output = mem::transmute::<_, u32>(point.y.to_bits()) as u64;
|
||||
output <<= 32;
|
||||
output |= mem::transmute::<_, u32>(point.x.to_bits()) as u64;
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
let unit_vertices = [
|
||||
to_float2_bits(point(0., 0.)),
|
||||
to_float2_bits(point(1., 0.)),
|
||||
to_float2_bits(point(0., 1.)),
|
||||
to_float2_bits(point(0., 1.)),
|
||||
to_float2_bits(point(1., 0.)),
|
||||
to_float2_bits(point(1., 1.)),
|
||||
];
|
||||
let unit_vertices = device.new_buffer_with_data(
|
||||
unit_vertices.as_ptr() as *const c_void,
|
||||
(unit_vertices.len() * mem::size_of::<u64>()) as u64,
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
let instances = device.new_buffer(
|
||||
INSTANCE_BUFFER_SIZE as u64,
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
|
||||
let quad_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
"quad",
|
||||
"quad_vertex",
|
||||
"quad_fragment",
|
||||
PIXEL_FORMAT,
|
||||
);
|
||||
|
||||
let command_queue = device.new_command_queue();
|
||||
Self {
|
||||
device,
|
||||
layer,
|
||||
command_queue,
|
||||
quad_pipeline_state,
|
||||
unit_vertices,
|
||||
instances,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer(&self) -> &metal::MetalLayerRef {
|
||||
&*self.layer
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
dbg!(scene);
|
||||
|
||||
let layer = self.layer.clone();
|
||||
let viewport_size = layer.drawable_size();
|
||||
let viewport_size: Size<DevicePixels> = size(
|
||||
(viewport_size.width.ceil() as u32).into(),
|
||||
(viewport_size.height.ceil() as u32).into(),
|
||||
);
|
||||
let drawable = if let Some(drawable) = layer.next_drawable() {
|
||||
drawable
|
||||
} else {
|
||||
log::error!(
|
||||
"failed to retrieve next drawable, drawable size: {:?}",
|
||||
viewport_size
|
||||
);
|
||||
return;
|
||||
};
|
||||
let command_queue = self.command_queue.clone();
|
||||
let command_buffer = command_queue.new_command_buffer();
|
||||
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
|
||||
let depth_texture_desc = metal::TextureDescriptor::new();
|
||||
depth_texture_desc.set_pixel_format(metal::MTLPixelFormat::Depth32Float);
|
||||
depth_texture_desc.set_storage_mode(metal::MTLStorageMode::Private);
|
||||
depth_texture_desc.set_usage(metal::MTLTextureUsage::RenderTarget);
|
||||
depth_texture_desc.set_width(u32::from(viewport_size.width) as u64);
|
||||
depth_texture_desc.set_height(u32::from(viewport_size.height) as u64);
|
||||
let depth_texture = self.device.new_texture(&depth_texture_desc);
|
||||
let depth_attachment = render_pass_descriptor.depth_attachment().unwrap();
|
||||
|
||||
depth_attachment.set_texture(Some(&depth_texture));
|
||||
depth_attachment.set_clear_depth(1.);
|
||||
depth_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
|
||||
color_attachment.set_texture(Some(drawable.texture()));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
|
||||
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
|
||||
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
|
||||
command_encoder.set_viewport(metal::MTLViewport {
|
||||
originX: 0.0,
|
||||
originY: 0.0,
|
||||
width: u32::from(viewport_size.width) as f64,
|
||||
height: u32::from(viewport_size.height) as f64,
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
});
|
||||
|
||||
let mut buffer_offset = 0;
|
||||
for layer in scene.layers() {
|
||||
self.draw_quads(
|
||||
&layer.quads,
|
||||
&mut buffer_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
);
|
||||
}
|
||||
|
||||
command_encoder.end_encoding();
|
||||
|
||||
self.instances.did_modify_range(NSRange {
|
||||
location: 0,
|
||||
length: buffer_offset as NSUInteger,
|
||||
});
|
||||
|
||||
command_buffer.commit();
|
||||
command_buffer.wait_until_completed();
|
||||
drawable.present();
|
||||
}
|
||||
|
||||
fn draw_quads(
|
||||
&mut self,
|
||||
quads: &[Quad],
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) {
|
||||
if quads.is_empty() {
|
||||
return;
|
||||
}
|
||||
align_offset(offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
QuadInputIndex::Vertices as u64,
|
||||
Some(&self.unit_vertices),
|
||||
0,
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
QuadInputIndex::Quads as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
QuadInputIndex::Quads as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
let quad_uniforms = QuadUniforms { viewport_size };
|
||||
|
||||
let quad_uniform_bytes = bytemuck::bytes_of(&quad_uniforms);
|
||||
command_encoder.set_vertex_bytes(
|
||||
QuadInputIndex::Uniforms as u64,
|
||||
quad_uniform_bytes.len() as u64,
|
||||
quad_uniform_bytes.as_ptr() as *const c_void,
|
||||
);
|
||||
|
||||
let quad_bytes = bytemuck::cast_slice(quads);
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(quad_bytes.as_ptr(), buffer_contents, quad_bytes.len());
|
||||
}
|
||||
|
||||
let next_offset = *offset + quad_bytes.len();
|
||||
assert!(
|
||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
"instance buffer exhausted"
|
||||
);
|
||||
|
||||
command_encoder.draw_primitives_instanced(
|
||||
metal::MTLPrimitiveType::Triangle,
|
||||
0,
|
||||
6,
|
||||
quads.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
}
|
||||
}
|
||||
|
||||
fn build_pipeline_state(
|
||||
device: &metal::DeviceRef,
|
||||
library: &metal::LibraryRef,
|
||||
label: &str,
|
||||
vertex_fn_name: &str,
|
||||
fragment_fn_name: &str,
|
||||
pixel_format: metal::MTLPixelFormat,
|
||||
) -> metal::RenderPipelineState {
|
||||
let vertex_fn = library
|
||||
.get_function(vertex_fn_name, None)
|
||||
.expect("error locating vertex function");
|
||||
let fragment_fn = library
|
||||
.get_function(fragment_fn_name, None)
|
||||
.expect("error locating fragment function");
|
||||
|
||||
let descriptor = metal::RenderPipelineDescriptor::new();
|
||||
descriptor.set_label(label);
|
||||
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
|
||||
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
|
||||
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
|
||||
color_attachment.set_pixel_format(pixel_format);
|
||||
color_attachment.set_blending_enabled(true);
|
||||
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
|
||||
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
|
||||
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
|
||||
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
|
||||
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
|
||||
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
|
||||
// descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Depth32Float);
|
||||
|
||||
device
|
||||
.new_render_pipeline_state(&descriptor)
|
||||
.expect("could not create render pipeline state")
|
||||
}
|
||||
|
||||
// Align to multiples of 256 make Metal happy.
|
||||
fn align_offset(offset: &mut usize) {
|
||||
*offset = ((*offset + 255) / 256) * 256;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
enum QuadInputIndex {
|
||||
Vertices = 0,
|
||||
Quads = 1,
|
||||
Uniforms = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct QuadUniforms {
|
||||
viewport_size: Size<DevicePixels>,
|
||||
}
|
394
crates/gpui3/src/platform/mac/open_type.rs
Normal file
394
crates/gpui3/src/platform/mac/open_type.rs
Normal file
@ -0,0 +1,394 @@
|
||||
#![allow(unused, non_upper_case_globals)]
|
||||
|
||||
use crate::FontFeatures;
|
||||
use cocoa::appkit::CGFloat;
|
||||
use core_foundation::{base::TCFType, number::CFNumber};
|
||||
use core_graphics::geometry::CGAffineTransform;
|
||||
use core_text::{
|
||||
font::{CTFont, CTFontRef},
|
||||
font_descriptor::{
|
||||
CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
|
||||
},
|
||||
};
|
||||
use font_kit::font::Font;
|
||||
use std::ptr;
|
||||
|
||||
const kCaseSensitiveLayoutOffSelector: i32 = 1;
|
||||
const kCaseSensitiveLayoutOnSelector: i32 = 0;
|
||||
const kCaseSensitiveLayoutType: i32 = 33;
|
||||
const kCaseSensitiveSpacingOffSelector: i32 = 3;
|
||||
const kCaseSensitiveSpacingOnSelector: i32 = 2;
|
||||
const kCharacterAlternativesType: i32 = 17;
|
||||
const kCommonLigaturesOffSelector: i32 = 3;
|
||||
const kCommonLigaturesOnSelector: i32 = 2;
|
||||
const kContextualAlternatesOffSelector: i32 = 1;
|
||||
const kContextualAlternatesOnSelector: i32 = 0;
|
||||
const kContextualAlternatesType: i32 = 36;
|
||||
const kContextualLigaturesOffSelector: i32 = 19;
|
||||
const kContextualLigaturesOnSelector: i32 = 18;
|
||||
const kContextualSwashAlternatesOffSelector: i32 = 5;
|
||||
const kContextualSwashAlternatesOnSelector: i32 = 4;
|
||||
const kDefaultLowerCaseSelector: i32 = 0;
|
||||
const kDefaultUpperCaseSelector: i32 = 0;
|
||||
const kDiagonalFractionsSelector: i32 = 2;
|
||||
const kFractionsType: i32 = 11;
|
||||
const kHistoricalLigaturesOffSelector: i32 = 21;
|
||||
const kHistoricalLigaturesOnSelector: i32 = 20;
|
||||
const kHojoCharactersSelector: i32 = 12;
|
||||
const kInferiorsSelector: i32 = 2;
|
||||
const kJIS2004CharactersSelector: i32 = 11;
|
||||
const kLigaturesType: i32 = 1;
|
||||
const kLowerCasePetiteCapsSelector: i32 = 2;
|
||||
const kLowerCaseSmallCapsSelector: i32 = 1;
|
||||
const kLowerCaseType: i32 = 37;
|
||||
const kLowerCaseNumbersSelector: i32 = 0;
|
||||
const kMathematicalGreekOffSelector: i32 = 11;
|
||||
const kMathematicalGreekOnSelector: i32 = 10;
|
||||
const kMonospacedNumbersSelector: i32 = 0;
|
||||
const kNLCCharactersSelector: i32 = 13;
|
||||
const kNoFractionsSelector: i32 = 0;
|
||||
const kNormalPositionSelector: i32 = 0;
|
||||
const kNoStyleOptionsSelector: i32 = 0;
|
||||
const kNumberCaseType: i32 = 21;
|
||||
const kNumberSpacingType: i32 = 6;
|
||||
const kOrdinalsSelector: i32 = 3;
|
||||
const kProportionalNumbersSelector: i32 = 1;
|
||||
const kQuarterWidthTextSelector: i32 = 4;
|
||||
const kScientificInferiorsSelector: i32 = 4;
|
||||
const kSlashedZeroOffSelector: i32 = 5;
|
||||
const kSlashedZeroOnSelector: i32 = 4;
|
||||
const kStyleOptionsType: i32 = 19;
|
||||
const kStylisticAltEighteenOffSelector: i32 = 37;
|
||||
const kStylisticAltEighteenOnSelector: i32 = 36;
|
||||
const kStylisticAltEightOffSelector: i32 = 17;
|
||||
const kStylisticAltEightOnSelector: i32 = 16;
|
||||
const kStylisticAltElevenOffSelector: i32 = 23;
|
||||
const kStylisticAltElevenOnSelector: i32 = 22;
|
||||
const kStylisticAlternativesType: i32 = 35;
|
||||
const kStylisticAltFifteenOffSelector: i32 = 31;
|
||||
const kStylisticAltFifteenOnSelector: i32 = 30;
|
||||
const kStylisticAltFiveOffSelector: i32 = 11;
|
||||
const kStylisticAltFiveOnSelector: i32 = 10;
|
||||
const kStylisticAltFourOffSelector: i32 = 9;
|
||||
const kStylisticAltFourOnSelector: i32 = 8;
|
||||
const kStylisticAltFourteenOffSelector: i32 = 29;
|
||||
const kStylisticAltFourteenOnSelector: i32 = 28;
|
||||
const kStylisticAltNineOffSelector: i32 = 19;
|
||||
const kStylisticAltNineOnSelector: i32 = 18;
|
||||
const kStylisticAltNineteenOffSelector: i32 = 39;
|
||||
const kStylisticAltNineteenOnSelector: i32 = 38;
|
||||
const kStylisticAltOneOffSelector: i32 = 3;
|
||||
const kStylisticAltOneOnSelector: i32 = 2;
|
||||
const kStylisticAltSevenOffSelector: i32 = 15;
|
||||
const kStylisticAltSevenOnSelector: i32 = 14;
|
||||
const kStylisticAltSeventeenOffSelector: i32 = 35;
|
||||
const kStylisticAltSeventeenOnSelector: i32 = 34;
|
||||
const kStylisticAltSixOffSelector: i32 = 13;
|
||||
const kStylisticAltSixOnSelector: i32 = 12;
|
||||
const kStylisticAltSixteenOffSelector: i32 = 33;
|
||||
const kStylisticAltSixteenOnSelector: i32 = 32;
|
||||
const kStylisticAltTenOffSelector: i32 = 21;
|
||||
const kStylisticAltTenOnSelector: i32 = 20;
|
||||
const kStylisticAltThirteenOffSelector: i32 = 27;
|
||||
const kStylisticAltThirteenOnSelector: i32 = 26;
|
||||
const kStylisticAltThreeOffSelector: i32 = 7;
|
||||
const kStylisticAltThreeOnSelector: i32 = 6;
|
||||
const kStylisticAltTwelveOffSelector: i32 = 25;
|
||||
const kStylisticAltTwelveOnSelector: i32 = 24;
|
||||
const kStylisticAltTwentyOffSelector: i32 = 41;
|
||||
const kStylisticAltTwentyOnSelector: i32 = 40;
|
||||
const kStylisticAltTwoOffSelector: i32 = 5;
|
||||
const kStylisticAltTwoOnSelector: i32 = 4;
|
||||
const kSuperiorsSelector: i32 = 1;
|
||||
const kSwashAlternatesOffSelector: i32 = 3;
|
||||
const kSwashAlternatesOnSelector: i32 = 2;
|
||||
const kTitlingCapsSelector: i32 = 4;
|
||||
const kTypographicExtrasType: i32 = 14;
|
||||
const kVerticalFractionsSelector: i32 = 1;
|
||||
const kVerticalPositionType: i32 = 10;
|
||||
|
||||
pub fn apply_features(font: &mut Font, features: FontFeatures) {
|
||||
// See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
|
||||
// for a reference implementation.
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.calt(),
|
||||
kContextualAlternatesType,
|
||||
kContextualAlternatesOnSelector,
|
||||
kContextualAlternatesOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.case(),
|
||||
kCaseSensitiveLayoutType,
|
||||
kCaseSensitiveLayoutOnSelector,
|
||||
kCaseSensitiveLayoutOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.cpsp(),
|
||||
kCaseSensitiveLayoutType,
|
||||
kCaseSensitiveSpacingOnSelector,
|
||||
kCaseSensitiveSpacingOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.frac(),
|
||||
kFractionsType,
|
||||
kDiagonalFractionsSelector,
|
||||
kNoFractionsSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.liga(),
|
||||
kLigaturesType,
|
||||
kCommonLigaturesOnSelector,
|
||||
kCommonLigaturesOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.onum(),
|
||||
kNumberCaseType,
|
||||
kLowerCaseNumbersSelector,
|
||||
2,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ordn(),
|
||||
kVerticalPositionType,
|
||||
kOrdinalsSelector,
|
||||
kNormalPositionSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.pnum(),
|
||||
kNumberSpacingType,
|
||||
kProportionalNumbersSelector,
|
||||
4,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss01(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltOneOnSelector,
|
||||
kStylisticAltOneOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss02(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTwoOnSelector,
|
||||
kStylisticAltTwoOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss03(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltThreeOnSelector,
|
||||
kStylisticAltThreeOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss04(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFourOnSelector,
|
||||
kStylisticAltFourOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss05(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFiveOnSelector,
|
||||
kStylisticAltFiveOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss06(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSixOnSelector,
|
||||
kStylisticAltSixOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss07(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSevenOnSelector,
|
||||
kStylisticAltSevenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss08(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltEightOnSelector,
|
||||
kStylisticAltEightOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss09(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltNineOnSelector,
|
||||
kStylisticAltNineOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss10(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTenOnSelector,
|
||||
kStylisticAltTenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss11(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltElevenOnSelector,
|
||||
kStylisticAltElevenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss12(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTwelveOnSelector,
|
||||
kStylisticAltTwelveOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss13(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltThirteenOnSelector,
|
||||
kStylisticAltThirteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss14(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFourteenOnSelector,
|
||||
kStylisticAltFourteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss15(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFifteenOnSelector,
|
||||
kStylisticAltFifteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss16(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSixteenOnSelector,
|
||||
kStylisticAltSixteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss17(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSeventeenOnSelector,
|
||||
kStylisticAltSeventeenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss18(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltEighteenOnSelector,
|
||||
kStylisticAltEighteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss19(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltNineteenOnSelector,
|
||||
kStylisticAltNineteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss20(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTwentyOnSelector,
|
||||
kStylisticAltTwentyOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.subs(),
|
||||
kVerticalPositionType,
|
||||
kInferiorsSelector,
|
||||
kNormalPositionSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.sups(),
|
||||
kVerticalPositionType,
|
||||
kSuperiorsSelector,
|
||||
kNormalPositionSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.swsh(),
|
||||
kContextualAlternatesType,
|
||||
kSwashAlternatesOnSelector,
|
||||
kSwashAlternatesOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.titl(),
|
||||
kStyleOptionsType,
|
||||
kTitlingCapsSelector,
|
||||
kNoStyleOptionsSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.tnum(),
|
||||
kNumberSpacingType,
|
||||
kMonospacedNumbersSelector,
|
||||
4,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.zero(),
|
||||
kTypographicExtrasType,
|
||||
kSlashedZeroOnSelector,
|
||||
kSlashedZeroOffSelector,
|
||||
);
|
||||
}
|
||||
|
||||
fn toggle_open_type_feature(
|
||||
font: &mut Font,
|
||||
enabled: Option<bool>,
|
||||
type_identifier: i32,
|
||||
on_selector_identifier: i32,
|
||||
off_selector_identifier: i32,
|
||||
) {
|
||||
if let Some(enabled) = enabled {
|
||||
let native_font = font.native_font();
|
||||
unsafe {
|
||||
let selector_identifier = if enabled {
|
||||
on_selector_identifier
|
||||
} else {
|
||||
off_selector_identifier
|
||||
};
|
||||
let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
|
||||
native_font.copy_descriptor().as_concrete_TypeRef(),
|
||||
CFNumber::from(type_identifier).as_concrete_TypeRef(),
|
||||
CFNumber::from(selector_identifier).as_concrete_TypeRef(),
|
||||
);
|
||||
let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
|
||||
let new_font = CTFontCreateCopyWithAttributes(
|
||||
font.native_font().as_concrete_TypeRef(),
|
||||
0.0,
|
||||
ptr::null(),
|
||||
new_descriptor.as_concrete_TypeRef(),
|
||||
);
|
||||
let new_font = CTFont::wrap_under_create_rule(new_font);
|
||||
*font = Font::from_native_font(new_font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "CoreText", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CTFontCreateCopyWithAttributes(
|
||||
font: CTFontRef,
|
||||
size: CGFloat,
|
||||
matrix: *const CGAffineTransform,
|
||||
attributes: CTFontDescriptorRef,
|
||||
) -> CTFontRef;
|
||||
}
|
1123
crates/gpui3/src/platform/mac/platform.rs
Normal file
1123
crates/gpui3/src/platform/mac/platform.rs
Normal file
File diff suppressed because it is too large
Load Diff
156
crates/gpui3/src/platform/mac/screen.rs
Normal file
156
crates/gpui3/src/platform/mac/screen.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use super::ns_string;
|
||||
use crate::{point, px, size, Bounds, Pixels, PlatformScreen, PlatformScreenHandle, ScreenId};
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
|
||||
};
|
||||
use core_foundation::{
|
||||
number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
|
||||
uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
|
||||
};
|
||||
use core_graphics::display::CGDirectDisplayID;
|
||||
use objc::runtime::Object;
|
||||
use std::{any::Any, ffi::c_void};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MacScreen {
|
||||
pub(crate) native_screen: id,
|
||||
}
|
||||
|
||||
unsafe impl Send for MacScreen {}
|
||||
|
||||
impl MacScreen {
|
||||
pub(crate) fn from_handle(handle: PlatformScreenHandle) -> Self {
|
||||
Self {
|
||||
native_screen: handle.0 as *mut Object,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the screen with the given UUID.
|
||||
pub fn find_by_id(id: ScreenId) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.id() == Some(id))
|
||||
}
|
||||
|
||||
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
||||
/// corner is at the origin of the AppKit coordinate system.
|
||||
fn primary() -> Self {
|
||||
Self::all().next().unwrap()
|
||||
}
|
||||
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
unsafe {
|
||||
let native_screens = NSScreen::screens(nil);
|
||||
(0..NSArray::count(native_screens)).map(move |ix| MacScreen {
|
||||
native_screen: native_screens.objectAtIndex(ix),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the given rectangle in screen coordinates from GPUI's
|
||||
/// coordinate system to the AppKit coordinate system.
|
||||
///
|
||||
/// In GPUI's coordinates, the origin is at the top left of the primary screen, with
|
||||
/// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
|
||||
/// bottom left of the primary screen, with the Y axis pointing upward.
|
||||
pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
|
||||
let primary_screen_height =
|
||||
px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
|
||||
|
||||
NSRect::new(
|
||||
NSPoint::new(
|
||||
bounds.origin.x.into(),
|
||||
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
|
||||
),
|
||||
NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert the given rectangle in screen coordinates from the AppKit
|
||||
/// coordinate system to GPUI's coordinate system.
|
||||
///
|
||||
/// In GPUI's coordinates, the origin is at the top left of the primary screen, with
|
||||
/// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
|
||||
/// bottom left of the primary screen, with the Y axis pointing upward.
|
||||
pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
|
||||
let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
|
||||
Bounds {
|
||||
origin: point(
|
||||
px(rect.origin.x as f32),
|
||||
px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
|
||||
),
|
||||
size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformScreen for MacScreen {
|
||||
fn id(&self) -> Option<ScreenId> {
|
||||
unsafe {
|
||||
// This approach is similar to that which winit takes
|
||||
// https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
|
||||
let device_description = self.native_screen.deviceDescription();
|
||||
|
||||
let key = ns_string("NSScreenNumber");
|
||||
let device_id_obj = device_description.objectForKey_(key);
|
||||
if device_id_obj.is_null() {
|
||||
// Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
|
||||
// to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut device_id: u32 = 0;
|
||||
CFNumberGetValue(
|
||||
device_id_obj as CFNumberRef,
|
||||
kCFNumberIntType,
|
||||
(&mut device_id) as *mut _ as *mut c_void,
|
||||
);
|
||||
let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
|
||||
if cfuuid.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = CFUUIDGetUUIDBytes(cfuuid);
|
||||
Some(ScreenId(Uuid::from_bytes([
|
||||
bytes.byte0,
|
||||
bytes.byte1,
|
||||
bytes.byte2,
|
||||
bytes.byte3,
|
||||
bytes.byte4,
|
||||
bytes.byte5,
|
||||
bytes.byte6,
|
||||
bytes.byte7,
|
||||
bytes.byte8,
|
||||
bytes.byte9,
|
||||
bytes.byte10,
|
||||
bytes.byte11,
|
||||
bytes.byte12,
|
||||
bytes.byte13,
|
||||
bytes.byte14,
|
||||
bytes.byte15,
|
||||
])))
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(&self) -> PlatformScreenHandle {
|
||||
PlatformScreenHandle(self.native_screen as *mut c_void)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
|
||||
}
|
||||
|
||||
fn content_bounds(&self) -> Bounds<Pixels> {
|
||||
unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
|
||||
}
|
||||
}
|
180
crates/gpui3/src/platform/mac/shaders.metal
Normal file
180
crates/gpui3/src/platform/mac/shaders.metal
Normal file
@ -0,0 +1,180 @@
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla);
|
||||
float4 to_device_position(float2 pixel_position, float2 viewport_size);
|
||||
|
||||
struct QuadVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 background_color;
|
||||
float4 border_color;
|
||||
uint quad_id;
|
||||
};
|
||||
|
||||
vertex QuadVertexOutput quad_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint quad_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(QuadInputIndex_Vertices)]],
|
||||
constant Quad *quads [[buffer(QuadInputIndex_Quads)]],
|
||||
constant QuadUniforms *uniforms [[buffer(QuadInputIndex_Uniforms)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Quad quad = quads[quad_id];
|
||||
float2 position_2d = unit_vertex * float2(quad.bounds.size.width, quad.bounds.size.height) + float2(quad.bounds.origin.x, quad.bounds.origin.y);
|
||||
position_2d.x = max(quad.clip_bounds.origin.x, position_2d.x);
|
||||
position_2d.x = min(quad.clip_bounds.origin.x + quad.clip_bounds.size.width, position_2d.x);
|
||||
position_2d.y = max(quad.clip_bounds.origin.y, position_2d.y);
|
||||
position_2d.y = min(quad.clip_bounds.origin.y + quad.clip_bounds.size.height, position_2d.y);
|
||||
|
||||
float2 viewport_size = float2((float)uniforms->viewport_size.width, (float)uniforms->viewport_size.height);
|
||||
float4 device_position = to_device_position(position_2d, viewport_size);
|
||||
float4 background_color = hsla_to_rgba(quad.background);
|
||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||
return QuadVertexOutput {
|
||||
device_position,
|
||||
background_color,
|
||||
border_color,
|
||||
quad_id
|
||||
};
|
||||
}
|
||||
|
||||
float quad_sdf(float2 point, Bounds_Pixels bounds, Corners_Pixels corner_radii) {
|
||||
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
|
||||
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
|
||||
float2 center_to_point = point - center;
|
||||
float corner_radius;
|
||||
if (center_to_point.x < 0.) {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
|
||||
float distance = length(max(0., rounded_edge_to_point))
|
||||
+ min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y))
|
||||
- corner_radius;
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(
|
||||
QuadVertexOutput input [[stage_in]],
|
||||
constant Quad *quads [[buffer(QuadInputIndex_Quads)]]
|
||||
) {
|
||||
Quad quad = quads[input.quad_id];
|
||||
float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
|
||||
float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
|
||||
float2 center_to_point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (center_to_point.x < 0.) {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = quad.corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = quad.corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = quad.corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = quad.corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
float2 rounded_edge_to_point = fabs(center_to_point) - half_size + corner_radius;
|
||||
float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
|
||||
|
||||
float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left : quad.border_widths.right;
|
||||
float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top : quad.border_widths.bottom;
|
||||
float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
|
||||
float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
|
||||
float border_width;
|
||||
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
|
||||
border_width = 0.;
|
||||
} else if (point_to_inset_corner.y > point_to_inset_corner.x) {
|
||||
border_width = horizontal_border;
|
||||
} else {
|
||||
border_width = vertical_border;
|
||||
}
|
||||
|
||||
float4 color;
|
||||
if (border_width == 0.) {
|
||||
color = input.background_color;
|
||||
} else {
|
||||
float inset_distance = distance + border_width;
|
||||
|
||||
// Decrease border's opacity as we move inside the background.
|
||||
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
|
||||
|
||||
// Alpha-blend the border and the background.
|
||||
float output_alpha = quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
|
||||
float3 premultiplied_border_rgb = input.border_color.rgb * quad.border_color.a;
|
||||
float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
|
||||
float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
|
||||
color = float4(premultiplied_output_rgb, output_alpha);
|
||||
}
|
||||
|
||||
float clip_distance = quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii);
|
||||
return color * float4(1., 1., 1., saturate(0.5 - distance) * saturate(0.5 - clip_distance));
|
||||
}
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla) {
|
||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||
float s = hsla.s;
|
||||
float l = hsla.l;
|
||||
float a = hsla.a;
|
||||
|
||||
float c = (1.0 - fabs(2.0*l - 1.0)) * s;
|
||||
float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
|
||||
float m = l - c/2.0;
|
||||
|
||||
float r = 0.0;
|
||||
float g = 0.0;
|
||||
float b = 0.0;
|
||||
|
||||
if (h >= 0.0 && h < 1.0) {
|
||||
r = c;
|
||||
g = x;
|
||||
b = 0.0;
|
||||
} else if (h >= 1.0 && h < 2.0) {
|
||||
r = x;
|
||||
g = c;
|
||||
b = 0.0;
|
||||
} else if (h >= 2.0 && h < 3.0) {
|
||||
r = 0.0;
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h >= 3.0 && h < 4.0) {
|
||||
r = 0.0;
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h >= 4.0 && h < 5.0) {
|
||||
r = x;
|
||||
g = 0.0;
|
||||
b = c;
|
||||
} else {
|
||||
r = c;
|
||||
g = 0.0;
|
||||
b = x;
|
||||
}
|
||||
|
||||
float4 rgba;
|
||||
rgba.x = (r + m);
|
||||
rgba.y = (g + m);
|
||||
rgba.z = (b + m);
|
||||
rgba.w = a;
|
||||
return rgba;
|
||||
}
|
||||
|
||||
float4 to_device_position(float2 pixel_position, float2 viewport_size) {
|
||||
return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
|
||||
}
|
754
crates/gpui3/src/platform/mac/text_system.rs
Normal file
754
crates/gpui3/src/platform/mac/text_system.rs
Normal file
@ -0,0 +1,754 @@
|
||||
use crate::{
|
||||
point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
|
||||
GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
|
||||
SharedString, Size,
|
||||
};
|
||||
use cocoa::appkit::{CGFloat, CGPoint};
|
||||
use collections::HashMap;
|
||||
use core_foundation::{
|
||||
array::CFIndex,
|
||||
attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
|
||||
base::{CFRange, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::{
|
||||
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
|
||||
color_space::CGColorSpace,
|
||||
context::CGContext,
|
||||
};
|
||||
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
|
||||
use font_kit::{
|
||||
font::Font as FontKitFont,
|
||||
handle::Handle,
|
||||
hinting::HintingOptions,
|
||||
metrics::Metrics,
|
||||
properties::{Style as FontkitStyle, Weight as FontkitWeight},
|
||||
source::SystemSource,
|
||||
sources::mem::MemSource,
|
||||
};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use pathfinder_geometry::{
|
||||
rect::{RectF, RectI},
|
||||
transform2d::Transform2F,
|
||||
vector::{Vector2F, Vector2I},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
|
||||
|
||||
use super::open_type;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kCGImageAlphaOnly: u32 = 7;
|
||||
|
||||
pub struct MacTextSystem(RwLock<MacTextSystemState>);
|
||||
|
||||
struct MacTextSystemState {
|
||||
memory_source: MemSource,
|
||||
system_source: SystemSource,
|
||||
fonts: Vec<FontKitFont>,
|
||||
font_selections: HashMap<Font, FontId>,
|
||||
font_ids_by_postscript_name: HashMap<String, FontId>,
|
||||
font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
|
||||
postscript_names_by_font_id: HashMap<FontId, String>,
|
||||
}
|
||||
|
||||
impl MacTextSystem {
|
||||
pub fn new() -> Self {
|
||||
Self(RwLock::new(MacTextSystemState {
|
||||
memory_source: MemSource::empty(),
|
||||
system_source: SystemSource::new(),
|
||||
fonts: Vec::new(),
|
||||
font_selections: HashMap::default(),
|
||||
font_ids_by_postscript_name: HashMap::default(),
|
||||
font_ids_by_family_name: HashMap::default(),
|
||||
postscript_names_by_font_id: HashMap::default(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MacTextSystem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformTextSystem for MacTextSystem {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
fn all_font_families(&self) -> Vec<String> {
|
||||
self.0
|
||||
.read()
|
||||
.system_source
|
||||
.all_families()
|
||||
.expect("core text should never return an error")
|
||||
}
|
||||
|
||||
fn font_id(&self, font: &Font) -> Result<FontId> {
|
||||
let lock = self.0.upgradable_read();
|
||||
if let Some(font_id) = lock.font_selections.get(font) {
|
||||
Ok(*font_id)
|
||||
} else {
|
||||
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
||||
let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
|
||||
{
|
||||
font_ids.as_slice()
|
||||
} else {
|
||||
let font_ids = lock.load_family(&font.family, font.features)?;
|
||||
lock.font_ids_by_family_name
|
||||
.insert(font.family.clone(), font_ids);
|
||||
lock.font_ids_by_family_name[&font.family].as_ref()
|
||||
};
|
||||
|
||||
let candidate_properties = candidates
|
||||
.iter()
|
||||
.map(|font_id| lock.fonts[font_id.0].properties())
|
||||
.collect::<SmallVec<[_; 4]>>();
|
||||
|
||||
let ix = font_kit::matching::find_best_match(
|
||||
&candidate_properties,
|
||||
&font_kit::properties::Properties {
|
||||
style: font.style.into(),
|
||||
weight: font.weight.into(),
|
||||
stretch: Default::default(),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(candidates[ix])
|
||||
}
|
||||
}
|
||||
|
||||
fn font_metrics(&self, font_id: FontId) -> FontMetrics {
|
||||
self.0.read().fonts[font_id.0].metrics().into()
|
||||
}
|
||||
|
||||
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
|
||||
Ok(self.0.read().fonts[font_id.0]
|
||||
.typographic_bounds(glyph_id.into())?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
|
||||
self.0.read().advance(font_id, glyph_id)
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.0.read().glyph_for_char(font_id, ch)
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_shift: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
options: RasterizationOptions,
|
||||
) -> Option<(Bounds<u32>, Vec<u8>)> {
|
||||
self.0.read().rasterize_glyph(
|
||||
font_id,
|
||||
font_size,
|
||||
glyph_id,
|
||||
subpixel_shift,
|
||||
scale_factor,
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
font_runs: &[(usize, FontId)],
|
||||
) -> LineLayout {
|
||||
self.0.write().layout_line(text, font_size, font_runs)
|
||||
}
|
||||
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
width: Pixels,
|
||||
) -> Vec<usize> {
|
||||
self.0.read().wrap_line(text, font_id, font_size, width)
|
||||
}
|
||||
}
|
||||
|
||||
impl MacTextSystemState {
|
||||
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
|
||||
self.memory_source.add_fonts(
|
||||
fonts
|
||||
.iter()
|
||||
.map(|bytes| Handle::from_memory(bytes.clone(), 0)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_family(
|
||||
&mut self,
|
||||
name: &SharedString,
|
||||
features: FontFeatures,
|
||||
) -> Result<SmallVec<[FontId; 4]>> {
|
||||
let mut font_ids = SmallVec::new();
|
||||
let family = self
|
||||
.memory_source
|
||||
.select_family_by_name(name.as_ref())
|
||||
.or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
|
||||
for font in family.fonts() {
|
||||
let mut font = font.load()?;
|
||||
open_type::apply_features(&mut font, features);
|
||||
let font_id = FontId(self.fonts.len());
|
||||
font_ids.push(font_id);
|
||||
let postscript_name = font.postscript_name().unwrap();
|
||||
self.font_ids_by_postscript_name
|
||||
.insert(postscript_name.clone(), font_id);
|
||||
self.postscript_names_by_font_id
|
||||
.insert(font_id, postscript_name);
|
||||
self.fonts.push(font);
|
||||
}
|
||||
Ok(font_ids)
|
||||
}
|
||||
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
|
||||
Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
|
||||
}
|
||||
|
||||
fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
|
||||
let postscript_name = requested_font.postscript_name();
|
||||
if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
|
||||
*font_id
|
||||
} else {
|
||||
let font_id = FontId(self.fonts.len());
|
||||
self.font_ids_by_postscript_name
|
||||
.insert(postscript_name.clone(), font_id);
|
||||
self.postscript_names_by_font_id
|
||||
.insert(font_id, postscript_name);
|
||||
self.fonts
|
||||
.push(font_kit::font::Font::from_core_graphics_font(
|
||||
requested_font.copy_to_CGFont(),
|
||||
));
|
||||
font_id
|
||||
}
|
||||
}
|
||||
|
||||
fn is_emoji(&self, font_id: FontId) -> bool {
|
||||
self.postscript_names_by_font_id
|
||||
.get(&font_id)
|
||||
.map_or(false, |postscript_name| {
|
||||
postscript_name == "AppleColorEmoji"
|
||||
})
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_shift: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
options: RasterizationOptions,
|
||||
) -> Option<(Bounds<u32>, Vec<u8>)> {
|
||||
let font = &self.fonts[font_id.0];
|
||||
let scale = Transform2F::from_scale(scale_factor);
|
||||
let glyph_bounds = font
|
||||
.raster_bounds(
|
||||
glyph_id.into(),
|
||||
font_size,
|
||||
scale,
|
||||
HintingOptions::None,
|
||||
font_kit::canvas::RasterizationOptions::GrayscaleAa,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
|
||||
None
|
||||
} else {
|
||||
// Make room for subpixel variants.
|
||||
let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
|
||||
let cx_bounds = RectI::new(
|
||||
glyph_bounds.origin(),
|
||||
glyph_bounds.size() + Vector2I::from(subpixel_padding),
|
||||
);
|
||||
|
||||
let mut bytes;
|
||||
let cx;
|
||||
match options {
|
||||
RasterizationOptions::Alpha => {
|
||||
bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
cx_bounds.width() as usize,
|
||||
cx_bounds.height() as usize,
|
||||
8,
|
||||
cx_bounds.width() as usize,
|
||||
&CGColorSpace::create_device_gray(),
|
||||
kCGImageAlphaOnly,
|
||||
);
|
||||
}
|
||||
RasterizationOptions::Bgra => {
|
||||
bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
cx_bounds.width() as usize,
|
||||
cx_bounds.height() as usize,
|
||||
8,
|
||||
cx_bounds.width() as usize * 4,
|
||||
&CGColorSpace::create_device_rgb(),
|
||||
kCGImageAlphaPremultipliedLast,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the origin to bottom left and account for scaling, this
|
||||
// makes drawing text consistent with the font-kit's raster_bounds.
|
||||
cx.translate(
|
||||
-glyph_bounds.origin_x() as CGFloat,
|
||||
(glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
|
||||
);
|
||||
cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
|
||||
|
||||
cx.set_allows_font_subpixel_positioning(true);
|
||||
cx.set_should_subpixel_position_fonts(true);
|
||||
cx.set_allows_font_subpixel_quantization(false);
|
||||
cx.set_should_subpixel_quantize_fonts(false);
|
||||
font.native_font()
|
||||
.clone_with_font_size(font_size as CGFloat)
|
||||
.draw_glyphs(
|
||||
&[u32::from(glyph_id) as CGGlyph],
|
||||
&[CGPoint::new(
|
||||
(f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
|
||||
(f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
|
||||
if let RasterizationOptions::Bgra = options {
|
||||
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
|
||||
for pixel in bytes.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
let a = pixel[3] as f32 / 255.;
|
||||
pixel[0] = (pixel[0] as f32 / a) as u8;
|
||||
pixel[1] = (pixel[1] as f32 / a) as u8;
|
||||
pixel[2] = (pixel[2] as f32 / a) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Some((cx_bounds.into(), bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&mut self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
font_runs: &[(usize, FontId)],
|
||||
) -> LineLayout {
|
||||
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
{
|
||||
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
|
||||
let utf16_line_len = string.char_len() as usize;
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
for (run_len, font_id) in font_runs {
|
||||
let utf8_end = ix_converter.utf8_ix + run_len;
|
||||
let utf16_start = ix_converter.utf16_ix;
|
||||
|
||||
if utf16_start >= utf16_line_len {
|
||||
break;
|
||||
}
|
||||
|
||||
ix_converter.advance_to_utf8_ix(utf8_end);
|
||||
let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
|
||||
|
||||
let cf_range =
|
||||
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
|
||||
|
||||
let font: &FontKitFont = &self.fonts[font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
kCTFontAttributeName,
|
||||
&font.native_font().clone_with_font_size(font_size.into()),
|
||||
);
|
||||
}
|
||||
|
||||
if utf16_end == utf16_line_len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
|
||||
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
|
||||
|
||||
let mut runs = Vec::new();
|
||||
for run in line.glyph_runs().into_iter() {
|
||||
let attributes = run.attributes().unwrap();
|
||||
let font = unsafe {
|
||||
attributes
|
||||
.get(kCTFontAttributeName)
|
||||
.downcast::<CTFont>()
|
||||
.unwrap()
|
||||
};
|
||||
let font_id = self.id_for_native_font(font);
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut glyphs = Vec::new();
|
||||
for ((glyph_id, position), glyph_utf16_ix) in run
|
||||
.glyphs()
|
||||
.iter()
|
||||
.zip(run.positions().iter())
|
||||
.zip(run.string_indices().iter())
|
||||
{
|
||||
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
|
||||
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
|
||||
glyphs.push(Glyph {
|
||||
id: (*glyph_id).into(),
|
||||
position: point(position.x as f32, position.y as f32).map(px),
|
||||
index: ix_converter.utf8_ix,
|
||||
is_emoji: self.is_emoji(font_id),
|
||||
});
|
||||
}
|
||||
|
||||
runs.push(Run { font_id, glyphs })
|
||||
}
|
||||
|
||||
let typographic_bounds = line.get_typographic_bounds();
|
||||
LineLayout {
|
||||
width: typographic_bounds.width.into(),
|
||||
ascent: typographic_bounds.ascent.into(),
|
||||
descent: typographic_bounds.descent.into(),
|
||||
runs,
|
||||
font_size,
|
||||
len: text.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
width: Pixels,
|
||||
) -> Vec<usize> {
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
|
||||
let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
|
||||
let font = &self.fonts[font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
kCTFontAttributeName,
|
||||
&font.native_font().clone_with_font_size(font_size.into()),
|
||||
);
|
||||
|
||||
let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut break_indices = Vec::new();
|
||||
while ix_converter.utf8_ix < text.len() {
|
||||
let utf16_len = CTTypesetterSuggestLineBreak(
|
||||
typesetter,
|
||||
ix_converter.utf16_ix as isize,
|
||||
width.into(),
|
||||
) as usize;
|
||||
ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
|
||||
if ix_converter.utf8_ix >= text.len() {
|
||||
break;
|
||||
}
|
||||
break_indices.push(ix_converter.utf8_ix as usize);
|
||||
}
|
||||
break_indices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StringIndexConverter<'a> {
|
||||
text: &'a str,
|
||||
utf8_ix: usize,
|
||||
utf16_ix: usize,
|
||||
}
|
||||
|
||||
impl<'a> StringIndexConverter<'a> {
|
||||
fn new(text: &'a str) -> Self {
|
||||
Self {
|
||||
text,
|
||||
utf8_ix: 0,
|
||||
utf16_ix: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
|
||||
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
|
||||
if self.utf8_ix + ix >= utf8_target {
|
||||
self.utf8_ix += ix;
|
||||
return;
|
||||
}
|
||||
self.utf16_ix += c.len_utf16();
|
||||
}
|
||||
self.utf8_ix = self.text.len();
|
||||
}
|
||||
|
||||
fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
|
||||
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
|
||||
if self.utf16_ix >= utf16_target {
|
||||
self.utf8_ix += ix;
|
||||
return;
|
||||
}
|
||||
self.utf16_ix += c.len_utf16();
|
||||
}
|
||||
self.utf8_ix = self.text.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CFTypesetter(c_void);
|
||||
|
||||
pub type CTTypesetterRef = *const __CFTypesetter;
|
||||
|
||||
#[link(name = "CoreText", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
|
||||
|
||||
fn CTTypesetterSuggestLineBreak(
|
||||
typesetter: CTTypesetterRef,
|
||||
start_index: CFIndex,
|
||||
width: f64,
|
||||
) -> CFIndex;
|
||||
}
|
||||
|
||||
impl From<Metrics> for FontMetrics {
|
||||
fn from(metrics: Metrics) -> Self {
|
||||
FontMetrics {
|
||||
units_per_em: metrics.units_per_em,
|
||||
ascent: metrics.ascent,
|
||||
descent: metrics.descent,
|
||||
line_gap: metrics.line_gap,
|
||||
underline_position: metrics.underline_position,
|
||||
underline_thickness: metrics.underline_thickness,
|
||||
cap_height: metrics.cap_height,
|
||||
x_height: metrics.x_height,
|
||||
bounding_box: metrics.bounding_box.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectF> for Bounds<f32> {
|
||||
fn from(rect: RectF) -> Self {
|
||||
Bounds {
|
||||
origin: point(rect.origin_x(), rect.origin_y()),
|
||||
size: size(rect.width(), rect.height()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectI> for Bounds<u32> {
|
||||
fn from(rect: RectI) -> Self {
|
||||
Bounds {
|
||||
origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
|
||||
size: size(rect.width() as u32, rect.height() as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point<u32>> for Vector2I {
|
||||
fn from(size: Point<u32>) -> Self {
|
||||
Vector2I::new(size.x as i32, size.y as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector2F> for Size<f32> {
|
||||
fn from(vec: Vector2F) -> Self {
|
||||
size(vec.x(), vec.y())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontWeight> for FontkitWeight {
|
||||
fn from(value: FontWeight) -> Self {
|
||||
FontkitWeight(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontStyle> for FontkitStyle {
|
||||
fn from(style: FontStyle) -> Self {
|
||||
match style {
|
||||
FontStyle::Normal => FontkitStyle::Normal,
|
||||
FontStyle::Italic => FontkitStyle::Italic,
|
||||
FontStyle::Oblique => FontkitStyle::Oblique,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::AppContext;
|
||||
// use font_kit::properties::{Style, Weight};
|
||||
// use platform::FontSystem as _;
|
||||
|
||||
// #[crate::test(self, retries = 5)]
|
||||
// fn test_layout_str(_: &mut AppContext) {
|
||||
// // This is failing intermittently on CI and we don't have time to figure it out
|
||||
// let fonts = FontSystem::new();
|
||||
// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
|
||||
// let menlo_regular = RunStyle {
|
||||
// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let menlo_italic = RunStyle {
|
||||
// font_id: fonts
|
||||
// .select_font(&menlo, Properties::new().style(Style::Italic))
|
||||
// .unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let menlo_bold = RunStyle {
|
||||
// font_id: fonts
|
||||
// .select_font(&menlo, Properties::new().weight(Weight::BOLD))
|
||||
// .unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// assert_ne!(menlo_regular, menlo_italic);
|
||||
// assert_ne!(menlo_regular, menlo_bold);
|
||||
// assert_ne!(menlo_italic, menlo_bold);
|
||||
|
||||
// let line = fonts.layout_line(
|
||||
// "hello world",
|
||||
// 16.0,
|
||||
// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
|
||||
// );
|
||||
// assert_eq!(line.runs.len(), 3);
|
||||
// assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
|
||||
// assert_eq!(line.runs[0].glyphs.len(), 2);
|
||||
// assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
|
||||
// assert_eq!(line.runs[1].glyphs.len(), 4);
|
||||
// assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
|
||||
// assert_eq!(line.runs[2].glyphs.len(), 5);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_glyph_offsets() -> crate::Result<()> {
|
||||
// let fonts = FontSystem::new();
|
||||
// let zapfino = fonts.load_family("Zapfino", &Default::default())?;
|
||||
// let zapfino_regular = RunStyle {
|
||||
// font_id: fonts.select_font(&zapfino, &Properties::new())?,
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let menlo = fonts.load_family("Menlo", &Default::default())?;
|
||||
// let menlo_regular = RunStyle {
|
||||
// font_id: fonts.select_font(&menlo, &Properties::new())?,
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
|
||||
// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
|
||||
// let line = fonts.layout_line(
|
||||
// text,
|
||||
// 16.0,
|
||||
// &[
|
||||
// (9, zapfino_regular),
|
||||
// (13, menlo_regular),
|
||||
// (text.len() - 22, zapfino_regular),
|
||||
// ],
|
||||
// );
|
||||
// assert_eq!(
|
||||
// line.runs
|
||||
// .iter()
|
||||
// .flat_map(|r| r.glyphs.iter())
|
||||
// .map(|g| g.index)
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
|
||||
// );
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn test_rasterize_glyph() {
|
||||
// use std::{fs::File, io::BufWriter, path::Path};
|
||||
|
||||
// let fonts = FontSystem::new();
|
||||
// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
|
||||
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
|
||||
|
||||
// const VARIANTS: usize = 1;
|
||||
// for i in 0..VARIANTS {
|
||||
// let variant = i as f32 / VARIANTS as f32;
|
||||
// let (bounds, bytes) = fonts
|
||||
// .rasterize_glyph(
|
||||
// font_id,
|
||||
// 16.0,
|
||||
// glyph_id,
|
||||
// vec2f(variant, variant),
|
||||
// 2.,
|
||||
// RasterizationOptions::Alpha,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
|
||||
// let path = Path::new(&name);
|
||||
// let file = File::create(path).unwrap();
|
||||
// let w = &mut BufWriter::new(file);
|
||||
|
||||
// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
|
||||
// encoder.set_color(png::ColorType::Grayscale);
|
||||
// encoder.set_depth(png::BitDepth::Eight);
|
||||
// let mut writer = encoder.write_header().unwrap();
|
||||
// writer.write_image_data(&bytes).unwrap();
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_wrap_line() {
|
||||
// let fonts = FontSystem::new();
|
||||
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
|
||||
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
|
||||
// let line = "one two three four five\n";
|
||||
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
|
||||
// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
|
||||
|
||||
// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
|
||||
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
|
||||
// assert_eq!(
|
||||
// wrap_boundaries,
|
||||
// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_layout_line_bom_char() {
|
||||
// let fonts = FontSystem::new();
|
||||
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
|
||||
// let style = RunStyle {
|
||||
// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
|
||||
// let line = "\u{feff}";
|
||||
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
// assert_eq!(layout.len, line.len());
|
||||
// assert!(layout.runs.is_empty());
|
||||
|
||||
// let line = "a\u{feff}b";
|
||||
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
// assert_eq!(layout.len, line.len());
|
||||
// assert_eq!(layout.runs.len(), 1);
|
||||
// assert_eq!(layout.runs[0].glyphs.len(), 2);
|
||||
// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
|
||||
// // There's no glyph for \u{feff}
|
||||
// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
|
||||
// }
|
||||
// }
|
1595
crates/gpui3/src/platform/mac/window.rs
Normal file
1595
crates/gpui3/src/platform/mac/window.rs
Normal file
File diff suppressed because it is too large
Load Diff
35
crates/gpui3/src/platform/mac/window_appearence.rs
Normal file
35
crates/gpui3/src/platform/mac/window_appearence.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use crate::WindowAppearance;
|
||||
use cocoa::{
|
||||
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
|
||||
base::id,
|
||||
foundation::NSString,
|
||||
};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use std::ffi::CStr;
|
||||
|
||||
impl WindowAppearance {
|
||||
pub unsafe fn from_native(appearance: id) -> Self {
|
||||
let name: id = msg_send![appearance, name];
|
||||
if name == NSAppearanceNameVibrantLight {
|
||||
Self::VibrantLight
|
||||
} else if name == NSAppearanceNameVibrantDark {
|
||||
Self::VibrantDark
|
||||
} else if name == NSAppearanceNameAqua {
|
||||
Self::Light
|
||||
} else if name == NSAppearanceNameDarkAqua {
|
||||
Self::Dark
|
||||
} else {
|
||||
println!(
|
||||
"unknown appearance: {:?}",
|
||||
CStr::from_ptr(name.UTF8String())
|
||||
);
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "AppKit", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static NSAppearanceNameAqua: id;
|
||||
pub static NSAppearanceNameDarkAqua: id;
|
||||
}
|
172
crates/gpui3/src/platform/test.rs
Normal file
172
crates/gpui3/src/platform/test.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use super::Platform;
|
||||
use crate::ScreenId;
|
||||
|
||||
pub struct TestPlatform;
|
||||
|
||||
impl TestPlatform {
|
||||
pub fn new() -> Self {
|
||||
TestPlatform
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("implement out what our tests needed in GPUI 1")
|
||||
impl Platform for TestPlatform {
|
||||
fn dispatcher(&self) -> std::sync::Arc<dyn crate::PlatformDispatcher> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn activate(&self, _ignoring_other_apps: bool) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn hide_other_apps(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unhide_other_apps(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn screen_by_id(&self, _id: ScreenId) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
_handle: crate::AnyWindowHandle,
|
||||
_options: crate::WindowOptions,
|
||||
) -> Box<dyn crate::PlatformWindow> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn open_url(&self, _url: &str) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
_options: crate::PathPromptOptions,
|
||||
) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn prompt_for_new_path(
|
||||
&self,
|
||||
_directory: &std::path::Path,
|
||||
) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reveal_path(&self, _path: &std::path::Path) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_quit(&self, _callback: Box<dyn FnMut()>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_event(&self, _callback: Box<dyn FnMut(crate::Event) -> bool>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn os_name(&self) -> &'static str {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn os_version(&self) -> anyhow::Result<crate::SemanticVersion> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn app_version(&self) -> anyhow::Result<crate::SemanticVersion> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn app_path(&self) -> anyhow::Result<std::path::PathBuf> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn local_timezone(&self) -> time::UtcOffset {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn path_for_auxiliary_executable(&self, _name: &str) -> anyhow::Result<std::path::PathBuf> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, _style: crate::CursorStyle) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn write_credentials(
|
||||
&self,
|
||||
_url: &str,
|
||||
_username: &str,
|
||||
_password: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn read_credentials(&self, _url: &str) -> anyhow::Result<Option<(String, Vec<u8>)>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, _url: &str) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
121
crates/gpui3/src/scene.rs
Normal file
121
crates/gpui3/src/scene.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use std::mem;
|
||||
|
||||
use super::{Bounds, Hsla, Pixels, Point};
|
||||
use crate::{Corners, Edges};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use collections::BTreeMap;
|
||||
|
||||
// Exported to metal
|
||||
pub type PointF = Point<f32>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scene {
|
||||
layers: BTreeMap<u32, SceneLayer>,
|
||||
pub(crate) scale_factor: f32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SceneLayer {
|
||||
pub quads: Vec<Quad>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub fn new(scale_factor: f32) -> Scene {
|
||||
Scene {
|
||||
layers: Default::default(),
|
||||
scale_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> Scene {
|
||||
Scene {
|
||||
layers: mem::take(&mut self.layers),
|
||||
scale_factor: self.scale_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, primitive: impl Into<Primitive>) {
|
||||
let mut primitive = primitive.into();
|
||||
primitive.scale(self.scale_factor);
|
||||
let layer = self.layers.entry(primitive.order()).or_default();
|
||||
match primitive {
|
||||
Primitive::Quad(quad) => layer.quads.push(quad),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layers(&self) -> impl Iterator<Item = &SceneLayer> {
|
||||
self.layers.values()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Primitive {
|
||||
Quad(Quad),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn order(&self) -> u32 {
|
||||
match self {
|
||||
Primitive::Quad(quad) => quad.order,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
match self {
|
||||
Primitive::Quad(quad) => {
|
||||
quad.background.is_transparent() && quad.border_color.is_transparent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale(&mut self, factor: f32) {
|
||||
match self {
|
||||
Primitive::Quad(quad) => {
|
||||
quad.scale(factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub clip_bounds: Bounds<Pixels>,
|
||||
pub clip_corner_radii: Corners<Pixels>,
|
||||
pub background: Hsla,
|
||||
pub border_color: Hsla,
|
||||
pub corner_radii: Corners<Pixels>,
|
||||
pub border_widths: Edges<Pixels>,
|
||||
}
|
||||
|
||||
impl Quad {
|
||||
pub fn vertices(&self) -> impl Iterator<Item = Point<Pixels>> {
|
||||
let x1 = self.bounds.origin.x;
|
||||
let y1 = self.bounds.origin.y;
|
||||
let x2 = x1 + self.bounds.size.width;
|
||||
let y2 = y1 + self.bounds.size.height;
|
||||
[
|
||||
Point::new(x1, y1),
|
||||
Point::new(x2, y1),
|
||||
Point::new(x2, y2),
|
||||
Point::new(x1, y2),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
pub fn scale(&mut self, factor: f32) {
|
||||
self.bounds *= factor;
|
||||
self.clip_bounds *= factor;
|
||||
self.clip_corner_radii *= factor;
|
||||
self.corner_radii *= factor;
|
||||
self.border_widths *= factor;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quad> for Primitive {
|
||||
fn from(quad: Quad) -> Self {
|
||||
Primitive::Quad(quad)
|
||||
}
|
||||
}
|
338
crates/gpui3/src/style.rs
Normal file
338
crates/gpui3/src/style.rs
Normal file
@ -0,0 +1,338 @@
|
||||
use crate::{
|
||||
phi, rems, AbsoluteLength, Bounds, Corners, CornersRefinement, DefiniteLength, Edges,
|
||||
EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point,
|
||||
PointRefinement, Quad, Rems, Result, RunStyle, SharedString, Size, SizeRefinement, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use refineable::Refineable;
|
||||
pub use taffy::style::{
|
||||
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
|
||||
Overflow, Position,
|
||||
};
|
||||
|
||||
#[derive(Clone, Refineable, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Style {
|
||||
/// What layout strategy should be used?
|
||||
pub display: Display,
|
||||
|
||||
// Overflow properties
|
||||
/// How children overflowing their container should affect layout
|
||||
#[refineable]
|
||||
pub overflow: Point<Overflow>,
|
||||
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
|
||||
pub scrollbar_width: f32,
|
||||
|
||||
// Position properties
|
||||
/// What should the `position` value of this struct use as a base offset?
|
||||
pub position: Position,
|
||||
/// How should the position of this element be tweaked relative to the layout defined?
|
||||
#[refineable]
|
||||
pub inset: Edges<Length>,
|
||||
|
||||
// Size properies
|
||||
/// Sets the initial size of the item
|
||||
#[refineable]
|
||||
pub size: Size<Length>,
|
||||
/// Controls the minimum size of the item
|
||||
#[refineable]
|
||||
pub min_size: Size<Length>,
|
||||
/// Controls the maximum size of the item
|
||||
#[refineable]
|
||||
pub max_size: Size<Length>,
|
||||
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
|
||||
pub aspect_ratio: Option<f32>,
|
||||
|
||||
// Spacing Properties
|
||||
/// How large should the margin be on each side?
|
||||
#[refineable]
|
||||
pub margin: Edges<Length>,
|
||||
/// How large should the padding be on each side?
|
||||
#[refineable]
|
||||
pub padding: Edges<DefiniteLength>,
|
||||
/// How large should the border be on each side?
|
||||
#[refineable]
|
||||
pub border_widths: Edges<AbsoluteLength>,
|
||||
|
||||
// Alignment properties
|
||||
/// How this node's children aligned in the cross/block axis?
|
||||
pub align_items: Option<AlignItems>,
|
||||
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
|
||||
pub align_self: Option<AlignSelf>,
|
||||
/// How should content contained within this item be aligned in the cross/block axis
|
||||
pub align_content: Option<AlignContent>,
|
||||
/// How should contained within this item be aligned in the main/inline axis
|
||||
pub justify_content: Option<JustifyContent>,
|
||||
/// How large should the gaps between items in a flex container be?
|
||||
#[refineable]
|
||||
pub gap: Size<DefiniteLength>,
|
||||
|
||||
// Flexbox properies
|
||||
/// Which direction does the main axis flow in?
|
||||
pub flex_direction: FlexDirection,
|
||||
/// Should elements wrap, or stay in a single line?
|
||||
pub flex_wrap: FlexWrap,
|
||||
/// Sets the initial main axis size of the item
|
||||
pub flex_basis: Length,
|
||||
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
|
||||
pub flex_grow: f32,
|
||||
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
|
||||
pub flex_shrink: f32,
|
||||
|
||||
/// The fill color of this element
|
||||
pub fill: Option<Fill>,
|
||||
|
||||
/// The border color of this element
|
||||
pub border_color: Option<Hsla>,
|
||||
|
||||
/// The radius of the corners of this element
|
||||
#[refineable]
|
||||
pub corner_radii: Corners<AbsoluteLength>,
|
||||
|
||||
/// TEXT
|
||||
pub text: TextStyleRefinement,
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct TextStyle {
|
||||
pub color: Hsla,
|
||||
pub font_family: SharedString,
|
||||
pub font_features: FontFeatures,
|
||||
pub font_size: Rems,
|
||||
pub line_height: DefiniteLength,
|
||||
pub font_weight: FontWeight,
|
||||
pub font_style: FontStyle,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
TextStyle {
|
||||
color: Hsla::default(),
|
||||
font_family: SharedString::default(),
|
||||
font_features: FontFeatures::default(),
|
||||
font_size: rems(1.),
|
||||
line_height: phi(),
|
||||
font_weight: FontWeight::default(),
|
||||
font_style: FontStyle::default(),
|
||||
underline: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
|
||||
if let Some(weight) = style.font_weight {
|
||||
self.font_weight = weight;
|
||||
}
|
||||
if let Some(style) = style.font_style {
|
||||
self.font_style = style;
|
||||
}
|
||||
|
||||
if let Some(color) = style.color {
|
||||
self.color = self.color.blend(color);
|
||||
}
|
||||
|
||||
if let Some(factor) = style.fade_out {
|
||||
self.color.fade_out(factor);
|
||||
}
|
||||
|
||||
if let Some(underline) = style.underline {
|
||||
self.underline = Some(underline);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn to_run(&self) -> RunStyle {
|
||||
RunStyle {
|
||||
font: Font {
|
||||
family: self.font_family.clone(),
|
||||
features: Default::default(),
|
||||
weight: self.font_weight,
|
||||
style: self.font_style,
|
||||
},
|
||||
color: self.color,
|
||||
underline: self.underline.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct HighlightStyle {
|
||||
pub color: Option<Hsla>,
|
||||
pub font_weight: Option<FontWeight>,
|
||||
pub font_style: Option<FontStyle>,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
pub fade_out: Option<f32>,
|
||||
}
|
||||
|
||||
impl Eq for HighlightStyle {}
|
||||
|
||||
impl Style {
|
||||
pub fn text_style(&self, _cx: &WindowContext) -> Option<&TextStyleRefinement> {
|
||||
if self.text.is_some() {
|
||||
Some(&self.text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Paints the background of an element styled with this style.
|
||||
pub fn paint<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
let background_color = self.fill.as_ref().and_then(Fill::color);
|
||||
if background_color.is_some() || self.is_border_visible() {
|
||||
cx.scene().insert(Quad {
|
||||
order,
|
||||
bounds,
|
||||
clip_bounds: bounds, // todo!
|
||||
clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
|
||||
background: background_color.unwrap_or_default(),
|
||||
border_color: self.border_color.unwrap_or_default(),
|
||||
corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
|
||||
border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn is_border_visible(&self) -> bool {
|
||||
self.border_color
|
||||
.map_or(false, |color| !color.is_transparent())
|
||||
&& self.border_widths.any(|length| !length.is_zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
display: Display::Block,
|
||||
overflow: Point {
|
||||
x: Overflow::Visible,
|
||||
y: Overflow::Visible,
|
||||
},
|
||||
scrollbar_width: 0.0,
|
||||
position: Position::Relative,
|
||||
inset: Edges::auto(),
|
||||
margin: Edges::<Length>::zero(),
|
||||
padding: Edges::<DefiniteLength>::zero(),
|
||||
border_widths: Edges::<AbsoluteLength>::zero(),
|
||||
size: Size::auto(),
|
||||
min_size: Size::auto(),
|
||||
max_size: Size::auto(),
|
||||
aspect_ratio: None,
|
||||
gap: Size::zero(),
|
||||
// Aligment
|
||||
align_items: None,
|
||||
align_self: None,
|
||||
align_content: None,
|
||||
justify_content: None,
|
||||
// Flexbox
|
||||
flex_direction: FlexDirection::Row,
|
||||
flex_wrap: FlexWrap::NoWrap,
|
||||
flex_grow: 0.0,
|
||||
flex_shrink: 1.0,
|
||||
flex_basis: Length::Auto,
|
||||
fill: None,
|
||||
border_color: None,
|
||||
corner_radii: Corners::default(),
|
||||
text: TextStyleRefinement::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[refineable(debug)]
|
||||
pub struct UnderlineStyle {
|
||||
pub thickness: Pixels,
|
||||
pub color: Option<Hsla>,
|
||||
pub squiggly: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Fill {
|
||||
Color(Hsla),
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn color(&self) -> Option<Hsla> {
|
||||
match self {
|
||||
Fill::Color(color) => Some(*color),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Fill {
|
||||
fn default() -> Self {
|
||||
Self::Color(Hsla::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for Fill {
|
||||
fn from(color: Hsla) -> Self {
|
||||
Self::Color(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextStyle> for HighlightStyle {
|
||||
fn from(other: TextStyle) -> Self {
|
||||
Self::from(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TextStyle> for HighlightStyle {
|
||||
fn from(other: &TextStyle) -> Self {
|
||||
Self {
|
||||
color: Some(other.color),
|
||||
font_weight: Some(other.font_weight),
|
||||
font_style: Some(other.font_style),
|
||||
underline: other.underline.clone(),
|
||||
fade_out: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HighlightStyle {
|
||||
pub fn highlight(&mut self, other: HighlightStyle) {
|
||||
match (self.color, other.color) {
|
||||
(Some(self_color), Some(other_color)) => {
|
||||
self.color = Some(Hsla::blend(other_color, self_color));
|
||||
}
|
||||
(None, Some(other_color)) => {
|
||||
self.color = Some(other_color);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if other.font_weight.is_some() {
|
||||
self.font_weight = other.font_weight;
|
||||
}
|
||||
|
||||
if other.font_style.is_some() {
|
||||
self.font_style = other.font_style;
|
||||
}
|
||||
|
||||
if other.underline.is_some() {
|
||||
self.underline = other.underline;
|
||||
}
|
||||
|
||||
match (other.fade_out, self.fade_out) {
|
||||
(Some(source_fade), None) => self.fade_out = Some(source_fade),
|
||||
(Some(source_fade), Some(dest_fade)) => {
|
||||
self.fade_out = Some((dest_fade * (1. + source_fade)).clamp(0., 1.));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for HighlightStyle {
|
||||
fn from(color: Hsla) -> Self {
|
||||
Self {
|
||||
color: Some(color),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
308
crates/gpui3/src/style_helpers.rs
Normal file
308
crates/gpui3/src/style_helpers.rs
Normal file
@ -0,0 +1,308 @@
|
||||
use crate::{
|
||||
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
|
||||
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
|
||||
};
|
||||
|
||||
pub trait StyleHelpers: Styled<Style = Style> {
|
||||
gpui3_macros::style_helpers!();
|
||||
|
||||
fn h(mut self, height: Length) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().size.height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
/// size_{n}: Sets width & height to {n}
|
||||
///
|
||||
/// Example:
|
||||
/// size_1: Sets width & height to 1
|
||||
fn size(mut self, size: Length) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().size.height = Some(size);
|
||||
self.declared_style().size.width = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
fn full(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().size.width = Some(relative(1.).into());
|
||||
self.declared_style().size.height = Some(relative(1.).into());
|
||||
self
|
||||
}
|
||||
|
||||
fn relative(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().position = Some(Position::Relative);
|
||||
self
|
||||
}
|
||||
|
||||
fn absolute(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().position = Some(Position::Absolute);
|
||||
self
|
||||
}
|
||||
|
||||
fn block(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().display = Some(Display::Block);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().display = Some(Display::Flex);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_col(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_direction = Some(FlexDirection::Column);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_row(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_direction = Some(FlexDirection::Row);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_1(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(relative(0.).into());
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_auto(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_initial(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(0.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_none(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(0.);
|
||||
self.declared_style().flex_shrink = Some(0.);
|
||||
self
|
||||
}
|
||||
|
||||
fn grow(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_start(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().align_items = Some(AlignItems::FlexStart);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_end(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().align_items = Some(AlignItems::FlexEnd);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_center(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().align_items = Some(AlignItems::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_between(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_center(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_start(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::Start);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_end(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::End);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_around(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
|
||||
self
|
||||
}
|
||||
|
||||
fn fill<F>(mut self, fill: F) -> Self
|
||||
where
|
||||
F: Into<Fill>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().fill = Some(fill.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn border_color<C>(mut self, border_color: C) -> Self
|
||||
where
|
||||
C: Into<Hsla>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().border_color = Some(border_color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
|
||||
let style: &mut StyleRefinement = self.declared_style();
|
||||
&mut style.text
|
||||
}
|
||||
|
||||
fn text_color(mut self, color: impl Into<Hsla>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xs(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.75));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_sm(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.875));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_base(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.0));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_lg(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.125));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.25));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_2xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.5));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_3xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.875));
|
||||
self
|
||||
}
|
||||
|
||||
fn font(mut self, family_name: impl Into<SharedString>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_family = Some(family_name.into());
|
||||
self
|
||||
}
|
||||
}
|
26
crates/gpui3/src/styled.rs
Normal file
26
crates/gpui3/src/styled.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use crate::{Refineable, RefinementCascade};
|
||||
|
||||
pub trait Styled {
|
||||
type Style: Refineable + Default;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
|
||||
|
||||
fn computed_style(&mut self) -> Self::Style {
|
||||
Self::Style::from_refinement(&self.style_cascade().merged())
|
||||
}
|
||||
|
||||
// fn hover(self) -> Hoverable<Self>
|
||||
// where
|
||||
// Self: Sized,
|
||||
// {
|
||||
// hoverable(self)
|
||||
// }
|
||||
|
||||
// fn active(self) -> Pressable<Self>
|
||||
// where
|
||||
// Self: Sized,
|
||||
// {
|
||||
// pressable(self)
|
||||
// }
|
||||
}
|
383
crates/gpui3/src/taffy.rs
Normal file
383
crates/gpui3/src/taffy.rs
Normal file
@ -0,0 +1,383 @@
|
||||
use super::{
|
||||
AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
|
||||
Style,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use taffy::{
|
||||
geometry::Size as TaffySize,
|
||||
style::AvailableSpace as TaffyAvailableSpace,
|
||||
tree::{Measurable, MeasureFunc, NodeId},
|
||||
Taffy,
|
||||
};
|
||||
|
||||
pub struct TaffyLayoutEngine {
|
||||
taffy: Taffy,
|
||||
children_to_parents: HashMap<LayoutId, LayoutId>,
|
||||
absolute_layouts: HashMap<LayoutId, Layout>,
|
||||
}
|
||||
|
||||
impl TaffyLayoutEngine {
|
||||
pub fn new() -> Self {
|
||||
TaffyLayoutEngine {
|
||||
taffy: Taffy::new(),
|
||||
children_to_parents: HashMap::default(),
|
||||
absolute_layouts: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
rem_size: Pixels,
|
||||
children: &[LayoutId],
|
||||
) -> Result<LayoutId> {
|
||||
let style = style.to_taffy(rem_size);
|
||||
if children.is_empty() {
|
||||
Ok(self.taffy.new_leaf(style)?.into())
|
||||
} else {
|
||||
let parent_id = self
|
||||
.taffy
|
||||
// This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
|
||||
.new_with_children(style, unsafe { std::mem::transmute(children) })?
|
||||
.into();
|
||||
for child_id in children {
|
||||
self.children_to_parents.insert(*child_id, parent_id);
|
||||
}
|
||||
Ok(parent_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_measured_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
rem_size: Pixels,
|
||||
measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Result<LayoutId> {
|
||||
let style = style.to_taffy(rem_size);
|
||||
|
||||
let measurable = Box::new(Measureable(measure)) as Box<dyn Measurable>;
|
||||
Ok(self
|
||||
.taffy
|
||||
.new_leaf_with_measure(style, MeasureFunc::Boxed(measurable))?
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn compute_layout(
|
||||
&mut self,
|
||||
id: LayoutId,
|
||||
available_space: Size<AvailableSpace>,
|
||||
) -> Result<()> {
|
||||
self.taffy
|
||||
.compute_layout(id.into(), available_space.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
|
||||
if let Some(layout) = self.absolute_layouts.get(&id).cloned() {
|
||||
return Ok(layout);
|
||||
}
|
||||
|
||||
let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?;
|
||||
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
|
||||
let parent_layout = self.layout(parent_id)?;
|
||||
relative_layout.bounds.origin += parent_layout.bounds.origin;
|
||||
}
|
||||
self.absolute_layouts.insert(id, relative_layout.clone());
|
||||
|
||||
Ok(relative_layout)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct LayoutId(NodeId);
|
||||
|
||||
impl std::hash::Hash for LayoutId {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
u64::from(self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NodeId> for LayoutId {
|
||||
fn from(node_id: NodeId) -> Self {
|
||||
Self(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayoutId> for NodeId {
|
||||
fn from(layout_id: LayoutId) -> NodeId {
|
||||
layout_id.0
|
||||
}
|
||||
}
|
||||
|
||||
struct Measureable<F>(F);
|
||||
|
||||
impl<F> taffy::tree::Measurable for Measureable<F>
|
||||
where
|
||||
F: Send + Sync + Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>,
|
||||
{
|
||||
fn measure(
|
||||
&self,
|
||||
known_dimensions: TaffySize<Option<f32>>,
|
||||
available_space: TaffySize<TaffyAvailableSpace>,
|
||||
) -> TaffySize<f32> {
|
||||
let known_dimensions: Size<Option<f32>> = known_dimensions.into();
|
||||
let known_dimensions: Size<Option<Pixels>> = known_dimensions.map(|d| d.map(Into::into));
|
||||
let available_space = available_space.into();
|
||||
let size = (self.0)(known_dimensions, available_space);
|
||||
size.into()
|
||||
}
|
||||
}
|
||||
|
||||
trait ToTaffy<Output> {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> Output;
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::Style> for Style {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
|
||||
taffy::style::Style {
|
||||
display: self.display,
|
||||
overflow: self.overflow.clone().into(),
|
||||
scrollbar_width: self.scrollbar_width,
|
||||
position: self.position,
|
||||
inset: self.inset.to_taffy(rem_size),
|
||||
size: self.size.to_taffy(rem_size),
|
||||
min_size: self.min_size.to_taffy(rem_size),
|
||||
max_size: self.max_size.to_taffy(rem_size),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
margin: self.margin.to_taffy(rem_size),
|
||||
padding: self.padding.to_taffy(rem_size),
|
||||
border: self.border_widths.to_taffy(rem_size),
|
||||
align_items: self.align_items,
|
||||
align_self: self.align_self,
|
||||
align_content: self.align_content,
|
||||
justify_content: self.justify_content,
|
||||
gap: self.gap.to_taffy(rem_size),
|
||||
flex_direction: self.flex_direction,
|
||||
flex_wrap: self.flex_wrap,
|
||||
flex_basis: self.flex_basis.to_taffy(rem_size),
|
||||
flex_grow: self.flex_grow,
|
||||
flex_shrink: self.flex_shrink,
|
||||
..Default::default() // Ignore grid properties for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl ToTaffy for Bounds<Length> {
|
||||
// type Output = taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto>;
|
||||
|
||||
// fn to_taffy(
|
||||
// &self,
|
||||
// rem_size: Pixels,
|
||||
// ) -> taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto> {
|
||||
// taffy::prelude::Bounds {
|
||||
// origin: self.origin.to_taffy(rem_size),
|
||||
// size: self.size.to_taffy(rem_size),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
|
||||
match self {
|
||||
Length::Definite(length) => length.to_taffy(rem_size),
|
||||
Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::Dimension> for Length {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
|
||||
match self {
|
||||
Length::Definite(length) => length.to_taffy(rem_size),
|
||||
Length::Auto => taffy::prelude::Dimension::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => match length {
|
||||
AbsoluteLength::Pixels(pixels) => {
|
||||
taffy::style::LengthPercentage::Length(pixels.into())
|
||||
}
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
|
||||
}
|
||||
},
|
||||
DefiniteLength::Fraction(fraction) => {
|
||||
taffy::style::LengthPercentage::Percent(*fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => match length {
|
||||
AbsoluteLength::Pixels(pixels) => {
|
||||
taffy::style::LengthPercentageAuto::Length(pixels.into())
|
||||
}
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::LengthPercentageAuto::Length((*rems * rem_size).into())
|
||||
}
|
||||
},
|
||||
DefiniteLength::Fraction(fraction) => {
|
||||
taffy::style::LengthPercentageAuto::Percent(*fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => match length {
|
||||
AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::Length(pixels.into()),
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::Dimension::Length((*rems * rem_size).into())
|
||||
}
|
||||
},
|
||||
DefiniteLength::Fraction(fraction) => taffy::style::Dimension::Percent(*fraction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(pixels.into()),
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, T2: Clone + Debug> From<taffy::geometry::Point<T>> for Point<T2>
|
||||
where
|
||||
T: Into<T2>,
|
||||
{
|
||||
fn from(point: taffy::geometry::Point<T>) -> Point<T2> {
|
||||
Point {
|
||||
x: point.x.into(),
|
||||
y: point.y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug, T2> Into<taffy::geometry::Point<T2>> for Point<T>
|
||||
where
|
||||
T: Into<T2>,
|
||||
{
|
||||
fn into(self) -> taffy::geometry::Point<T2> {
|
||||
taffy::geometry::Point {
|
||||
x: self.x.into(),
|
||||
y: self.y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToTaffy<U> + Clone + Debug, U> ToTaffy<taffy::geometry::Size<U>> for Size<T> {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Size<U> {
|
||||
taffy::geometry::Size {
|
||||
width: self.width.to_taffy(rem_size).into(),
|
||||
height: self.height.to_taffy(rem_size).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ToTaffy<taffy::geometry::Rect<U>> for Edges<T>
|
||||
where
|
||||
T: ToTaffy<U> + Clone + Debug,
|
||||
{
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Rect<U> {
|
||||
taffy::geometry::Rect {
|
||||
top: self.top.to_taffy(rem_size).into(),
|
||||
right: self.right.to_taffy(rem_size).into(),
|
||||
bottom: self.bottom.to_taffy(rem_size).into(),
|
||||
left: self.left.to_taffy(rem_size).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<U>, U: Clone + Debug> From<TaffySize<T>> for Size<U> {
|
||||
fn from(taffy_size: taffy::geometry::Size<T>) -> Self {
|
||||
Size {
|
||||
width: taffy_size.width.into(),
|
||||
height: taffy_size.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<U> + Clone + Debug, U> From<Size<T>> for taffy::geometry::Size<U> {
|
||||
fn from(size: Size<T>) -> Self {
|
||||
taffy::geometry::Size {
|
||||
width: size.width.into(),
|
||||
height: size.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl From<TaffySize<Option<f32>>> for Size<Option<Pixels>> {
|
||||
// fn from(value: TaffySize<Option<f32>>) -> Self {
|
||||
// Self {
|
||||
// width: value.width.map(Into::into),
|
||||
// height: value.height.map(Into::into),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AvailableSpace {
|
||||
/// The amount of space available is the specified number of pixels
|
||||
Definite(Pixels),
|
||||
/// The amount of space available is indefinite and the node should be laid out under a min-content constraint
|
||||
MinContent,
|
||||
/// The amount of space available is indefinite and the node should be laid out under a max-content constraint
|
||||
MaxContent,
|
||||
}
|
||||
|
||||
impl From<AvailableSpace> for TaffyAvailableSpace {
|
||||
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
|
||||
match space {
|
||||
AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
|
||||
AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
|
||||
AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaffyAvailableSpace> for AvailableSpace {
|
||||
fn from(space: TaffyAvailableSpace) -> AvailableSpace {
|
||||
match space {
|
||||
TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
|
||||
TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
|
||||
TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for AvailableSpace {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
AvailableSpace::Definite(pixels)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&taffy::tree::Layout> for Layout {
|
||||
fn from(layout: &taffy::tree::Layout) -> Self {
|
||||
Layout {
|
||||
order: layout.order,
|
||||
bounds: Bounds {
|
||||
origin: layout.location.into(),
|
||||
size: layout.size.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
477
crates/gpui3/src/text_system.rs
Normal file
477
crates/gpui3/src/text_system.rs
Normal file
@ -0,0 +1,477 @@
|
||||
mod font_features;
|
||||
mod line_wrapper;
|
||||
mod text_layout_cache;
|
||||
|
||||
use anyhow::anyhow;
|
||||
pub use font_features::*;
|
||||
use line_wrapper::*;
|
||||
pub use text_layout_cache::*;
|
||||
|
||||
use crate::{
|
||||
px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use core::fmt;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub struct FontId(pub usize);
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub struct FontFamilyId(pub usize);
|
||||
|
||||
pub struct TextSystem {
|
||||
text_layout_cache: Arc<TextLayoutCache>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
||||
fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
|
||||
font_metrics: RwLock<HashMap<Font, FontMetrics>>,
|
||||
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
||||
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
|
||||
}
|
||||
|
||||
impl TextSystem {
|
||||
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
TextSystem {
|
||||
text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
|
||||
platform_text_system,
|
||||
font_metrics: RwLock::new(HashMap::default()),
|
||||
font_ids_by_font: RwLock::new(HashMap::default()),
|
||||
fonts_by_font_id: RwLock::new(HashMap::default()),
|
||||
wrapper_pool: Mutex::new(HashMap::default()),
|
||||
font_runs_pool: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font_id(&self, font: &Font) -> Result<FontId> {
|
||||
let font_id = self.font_ids_by_font.read().get(font).copied();
|
||||
|
||||
if let Some(font_id) = font_id {
|
||||
Ok(font_id)
|
||||
} else {
|
||||
let font_id = self.platform_text_system.font_id(font)?;
|
||||
self.font_ids_by_font.write().insert(font.clone(), font_id);
|
||||
self.fonts_by_font_id.write().insert(font_id, font.clone());
|
||||
Ok(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
|
||||
self.fonts_by_font_id
|
||||
.read()
|
||||
.get(&font_id)
|
||||
.ok_or_else(|| anyhow!("font not found"))
|
||||
.map(|font| f(self, font))
|
||||
}
|
||||
|
||||
pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
|
||||
self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
|
||||
}
|
||||
|
||||
pub fn typographic_bounds(
|
||||
&self,
|
||||
font: &Font,
|
||||
font_size: Pixels,
|
||||
character: char,
|
||||
) -> Result<Bounds<Pixels>> {
|
||||
let font_id = self.font_id(font)?;
|
||||
let glyph_id = self
|
||||
.platform_text_system
|
||||
.glyph_for_char(font_id, character)
|
||||
.ok_or_else(|| anyhow!("glyph not found for character '{}'", character))?;
|
||||
let bounds = self
|
||||
.platform_text_system
|
||||
.typographic_bounds(font_id, glyph_id)?;
|
||||
self.read_metrics(font, |metrics| {
|
||||
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
|
||||
let font_id = self.font_id(font)?;
|
||||
let glyph_id = self
|
||||
.platform_text_system
|
||||
.glyph_for_char(font_id, ch)
|
||||
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
||||
let result =
|
||||
self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32;
|
||||
|
||||
Ok(result * font_size)
|
||||
}
|
||||
|
||||
pub fn units_per_em(&self, font: &Font) -> Result<u32> {
|
||||
self.read_metrics(font, |metrics| metrics.units_per_em as u32)
|
||||
}
|
||||
|
||||
pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
||||
self.read_metrics(font, |metrics| metrics.cap_height(font_size))
|
||||
}
|
||||
|
||||
pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
||||
self.read_metrics(font, |metrics| metrics.x_height(font_size))
|
||||
}
|
||||
|
||||
pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
||||
self.read_metrics(font, |metrics| metrics.ascent(font_size))
|
||||
}
|
||||
|
||||
pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
||||
self.read_metrics(font, |metrics| metrics.descent(font_size))
|
||||
}
|
||||
|
||||
pub fn baseline_offset(
|
||||
&self,
|
||||
font: &Font,
|
||||
font_size: Pixels,
|
||||
line_height: Pixels,
|
||||
) -> Result<Pixels> {
|
||||
let ascent = self.ascent(font, font_size)?;
|
||||
let descent = self.descent(font, font_size)?;
|
||||
let padding_top = (line_height - ascent - descent) / 2.;
|
||||
Ok(padding_top + ascent)
|
||||
}
|
||||
|
||||
fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
|
||||
let lock = self.font_metrics.upgradable_read();
|
||||
|
||||
if let Some(metrics) = lock.get(font) {
|
||||
Ok(read(metrics))
|
||||
} else {
|
||||
let font_id = self.platform_text_system.font_id(&font)?;
|
||||
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
||||
let metrics = lock
|
||||
.entry(font.clone())
|
||||
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
|
||||
Ok(read(metrics))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
runs: &[(usize, RunStyle)],
|
||||
) -> Result<Line> {
|
||||
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
||||
|
||||
dbg!("got font runs from pool");
|
||||
let mut last_font: Option<&Font> = None;
|
||||
for (len, style) in runs {
|
||||
dbg!(len);
|
||||
if let Some(last_font) = last_font.as_ref() {
|
||||
dbg!("a");
|
||||
if **last_font == style.font {
|
||||
dbg!("b");
|
||||
font_runs.last_mut().unwrap().0 += len;
|
||||
dbg!("c");
|
||||
continue;
|
||||
}
|
||||
dbg!("d");
|
||||
}
|
||||
dbg!("e");
|
||||
last_font = Some(&style.font);
|
||||
dbg!("f");
|
||||
font_runs.push((*len, self.font_id(&style.font)?));
|
||||
dbg!("g");
|
||||
}
|
||||
|
||||
dbg!("built font runs");
|
||||
|
||||
let layout = self
|
||||
.text_layout_cache
|
||||
.layout_line(text, font_size, &font_runs);
|
||||
|
||||
font_runs.clear();
|
||||
self.font_runs_pool.lock().push(font_runs);
|
||||
|
||||
Ok(Line::new(layout.clone(), runs))
|
||||
}
|
||||
|
||||
pub fn finish_frame(&self) {
|
||||
self.text_layout_cache.finish_frame()
|
||||
}
|
||||
|
||||
pub fn line_wrapper(
|
||||
self: &Arc<Self>,
|
||||
font: Font,
|
||||
font_size: Pixels,
|
||||
) -> Result<LineWrapperHandle> {
|
||||
let lock = &mut self.wrapper_pool.lock();
|
||||
let font_id = self.font_id(&font)?;
|
||||
let wrappers = lock
|
||||
.entry(FontIdWithSize { font_id, font_size })
|
||||
.or_default();
|
||||
let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| {
|
||||
Ok(LineWrapper::new(
|
||||
font_id,
|
||||
font_size,
|
||||
self.platform_text_system.clone(),
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(LineWrapperHandle {
|
||||
wrapper: Some(wrapper),
|
||||
text_system: self.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
struct FontIdWithSize {
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
}
|
||||
|
||||
pub struct LineWrapperHandle {
|
||||
wrapper: Option<LineWrapper>,
|
||||
text_system: Arc<TextSystem>,
|
||||
}
|
||||
|
||||
impl Drop for LineWrapperHandle {
|
||||
fn drop(&mut self) {
|
||||
let mut state = self.text_system.wrapper_pool.lock();
|
||||
let wrapper = self.wrapper.take().unwrap();
|
||||
state
|
||||
.get_mut(&FontIdWithSize {
|
||||
font_id: wrapper.font_id.clone(),
|
||||
font_size: wrapper.font_size,
|
||||
})
|
||||
.unwrap()
|
||||
.push(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LineWrapperHandle {
|
||||
type Target = LineWrapper;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.wrapper.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for LineWrapperHandle {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.wrapper.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
|
||||
/// with 400.0 as normal.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
pub struct FontWeight(pub f32);
|
||||
|
||||
impl Default for FontWeight {
|
||||
#[inline]
|
||||
fn default() -> FontWeight {
|
||||
FontWeight::NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for FontWeight {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FontWeight {}
|
||||
|
||||
impl FontWeight {
|
||||
/// Thin weight (100), the thinnest value.
|
||||
pub const THIN: FontWeight = FontWeight(100.0);
|
||||
/// Extra light weight (200).
|
||||
pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
|
||||
/// Light weight (300).
|
||||
pub const LIGHT: FontWeight = FontWeight(300.0);
|
||||
/// Normal (400).
|
||||
pub const NORMAL: FontWeight = FontWeight(400.0);
|
||||
/// Medium weight (500, higher than normal).
|
||||
pub const MEDIUM: FontWeight = FontWeight(500.0);
|
||||
/// Semibold weight (600).
|
||||
pub const SEMIBOLD: FontWeight = FontWeight(600.0);
|
||||
/// Bold weight (700).
|
||||
pub const BOLD: FontWeight = FontWeight(700.0);
|
||||
/// Extra-bold weight (800).
|
||||
pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
|
||||
/// Black weight (900), the thickest value.
|
||||
pub const BLACK: FontWeight = FontWeight(900.0);
|
||||
}
|
||||
|
||||
/// Allows italic or oblique faces to be selected.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
|
||||
pub enum FontStyle {
|
||||
/// A face that is neither italic not obliqued.
|
||||
Normal,
|
||||
/// A form that is generally cursive in nature.
|
||||
Italic,
|
||||
/// A typically-sloped version of the regular face.
|
||||
Oblique,
|
||||
}
|
||||
|
||||
impl Default for FontStyle {
|
||||
fn default() -> FontStyle {
|
||||
FontStyle::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FontStyle {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RunStyle {
|
||||
pub font: Font,
|
||||
pub color: Hsla,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct GlyphId(u32);
|
||||
|
||||
impl From<GlyphId> for u32 {
|
||||
fn from(value: GlyphId) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for GlyphId {
|
||||
fn from(num: u16) -> Self {
|
||||
GlyphId(num as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for GlyphId {
|
||||
fn from(num: u32) -> Self {
|
||||
GlyphId(num)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Glyph {
|
||||
pub id: GlyphId,
|
||||
pub position: Point<Pixels>,
|
||||
pub index: usize,
|
||||
pub is_emoji: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LineLayout {
|
||||
pub font_size: Pixels,
|
||||
pub width: Pixels,
|
||||
pub ascent: Pixels,
|
||||
pub descent: Pixels,
|
||||
pub runs: Vec<Run>,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Run {
|
||||
pub font_id: FontId,
|
||||
pub glyphs: Vec<Glyph>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Font {
|
||||
pub family: SharedString,
|
||||
pub features: FontFeatures,
|
||||
pub weight: FontWeight,
|
||||
pub style: FontStyle,
|
||||
}
|
||||
|
||||
pub fn font(family: impl Into<SharedString>) -> Font {
|
||||
Font {
|
||||
family: family.into(),
|
||||
features: FontFeatures::default(),
|
||||
weight: FontWeight::default(),
|
||||
style: FontStyle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Font {
|
||||
pub fn bold(mut self) -> Self {
|
||||
self.weight = FontWeight::BOLD;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for storing font metrics.
|
||||
/// It is used to define the measurements of a typeface.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FontMetrics {
|
||||
/// The number of font units that make up the "em square",
|
||||
/// a scalable grid for determining the size of a typeface.
|
||||
pub(crate) units_per_em: u32,
|
||||
|
||||
/// The vertical distance from the baseline of the font to the top of the glyph covers.
|
||||
pub(crate) ascent: f32,
|
||||
|
||||
/// The vertical distance from the baseline of the font to the bottom of the glyph covers.
|
||||
pub(crate) descent: f32,
|
||||
|
||||
/// The recommended additional space to add between lines of type.
|
||||
pub(crate) line_gap: f32,
|
||||
|
||||
/// The suggested position of the underline.
|
||||
pub(crate) underline_position: f32,
|
||||
|
||||
/// The suggested thickness of the underline.
|
||||
pub(crate) underline_thickness: f32,
|
||||
|
||||
/// The height of a capital letter measured from the baseline of the font.
|
||||
pub(crate) cap_height: f32,
|
||||
|
||||
/// The height of a lowercase x.
|
||||
pub(crate) x_height: f32,
|
||||
|
||||
/// The outer limits of the area that the font covers.
|
||||
pub(crate) bounding_box: Bounds<f32>,
|
||||
}
|
||||
|
||||
impl FontMetrics {
|
||||
/// Returns the vertical distance from the baseline of the font to the top of the glyph covers in pixels.
|
||||
pub fn ascent(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the vertical distance from the baseline of the font to the bottom of the glyph covers in pixels.
|
||||
pub fn descent(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.descent / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the recommended additional space to add between lines of type in pixels.
|
||||
pub fn line_gap(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the suggested position of the underline in pixels.
|
||||
pub fn underline_position(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the suggested thickness of the underline in pixels.
|
||||
pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the height of a capital letter measured from the baseline of the font in pixels.
|
||||
pub fn cap_height(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the height of a lowercase x in pixels.
|
||||
pub fn x_height(&self, font_size: Pixels) -> Pixels {
|
||||
Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
|
||||
}
|
||||
|
||||
/// Returns the outer limits of the area that the font covers in pixels.
|
||||
pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
|
||||
(self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
|
||||
}
|
||||
}
|
162
crates/gpui3/src/text_system/font_features.rs
Normal file
162
crates/gpui3/src/text_system/font_features.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use schemars::{
|
||||
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
|
||||
JsonSchema,
|
||||
};
|
||||
|
||||
macro_rules! create_definitions {
|
||||
($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct FontFeatures {
|
||||
enabled: u64,
|
||||
disabled: u64,
|
||||
}
|
||||
|
||||
impl FontFeatures {
|
||||
$(
|
||||
pub fn $name(&self) -> Option<bool> {
|
||||
if (self.enabled & (1 << $idx)) != 0 {
|
||||
Some(true)
|
||||
} else if (self.disabled & (1 << $idx)) != 0 {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FontFeatures {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut debug = f.debug_struct("FontFeatures");
|
||||
$(
|
||||
if let Some(value) = self.$name() {
|
||||
debug.field(stringify!($name), &value);
|
||||
};
|
||||
)*
|
||||
debug.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for FontFeatures {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{MapAccess, Visitor};
|
||||
use std::fmt;
|
||||
|
||||
struct FontFeaturesVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for FontFeaturesVisitor {
|
||||
type Value = FontFeatures;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a map of font features")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut enabled: u64 = 0;
|
||||
let mut disabled: u64 = 0;
|
||||
|
||||
while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
|
||||
let idx = match key.as_str() {
|
||||
$(stringify!($name) => $idx,)*
|
||||
_ => continue,
|
||||
};
|
||||
match value {
|
||||
Some(true) => enabled |= 1 << idx,
|
||||
Some(false) => disabled |= 1 << idx,
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
Ok(FontFeatures { enabled, disabled })
|
||||
}
|
||||
}
|
||||
|
||||
let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
|
||||
Ok(features)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for FontFeatures {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
|
||||
$(
|
||||
let feature = stringify!($name);
|
||||
if let Some(value) = self.$name() {
|
||||
map.serialize_entry(feature, &value)?;
|
||||
}
|
||||
)*
|
||||
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for FontFeatures {
|
||||
fn schema_name() -> String {
|
||||
"FontFeatures".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
let mut schema = SchemaObject::default();
|
||||
let properties = &mut schema.object().properties;
|
||||
let feature_schema = Schema::Object(SchemaObject {
|
||||
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
$(
|
||||
properties.insert(stringify!($name).to_owned(), feature_schema.clone());
|
||||
)*
|
||||
|
||||
schema.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
create_definitions!(
|
||||
(calt, 0),
|
||||
(case, 1),
|
||||
(cpsp, 2),
|
||||
(frac, 3),
|
||||
(liga, 4),
|
||||
(onum, 5),
|
||||
(ordn, 6),
|
||||
(pnum, 7),
|
||||
(ss01, 8),
|
||||
(ss02, 9),
|
||||
(ss03, 10),
|
||||
(ss04, 11),
|
||||
(ss05, 12),
|
||||
(ss06, 13),
|
||||
(ss07, 14),
|
||||
(ss08, 15),
|
||||
(ss09, 16),
|
||||
(ss10, 17),
|
||||
(ss11, 18),
|
||||
(ss12, 19),
|
||||
(ss13, 20),
|
||||
(ss14, 21),
|
||||
(ss15, 22),
|
||||
(ss16, 23),
|
||||
(ss17, 24),
|
||||
(ss18, 25),
|
||||
(ss19, 26),
|
||||
(ss20, 27),
|
||||
(subs, 28),
|
||||
(sups, 29),
|
||||
(swsh, 30),
|
||||
(titl, 31),
|
||||
(tnum, 32),
|
||||
(zero, 33)
|
||||
);
|
329
crates/gpui3/src/text_system/line_wrapper.rs
Normal file
329
crates/gpui3/src/text_system/line_wrapper.rs
Normal file
@ -0,0 +1,329 @@
|
||||
use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
|
||||
use collections::HashMap;
|
||||
use std::{iter, sync::Arc};
|
||||
|
||||
pub struct LineWrapper {
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
pub(crate) font_id: FontId,
|
||||
pub(crate) font_size: Pixels,
|
||||
cached_ascii_char_widths: [Option<Pixels>; 128],
|
||||
cached_other_char_widths: HashMap<char, Pixels>,
|
||||
}
|
||||
|
||||
impl LineWrapper {
|
||||
pub const MAX_INDENT: u32 = 256;
|
||||
|
||||
pub fn new(
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
text_system: Arc<dyn PlatformTextSystem>,
|
||||
) -> Self {
|
||||
Self {
|
||||
platform_text_system: text_system,
|
||||
font_id,
|
||||
font_size,
|
||||
cached_ascii_char_widths: [None; 128],
|
||||
cached_other_char_widths: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_line<'a>(
|
||||
&'a mut self,
|
||||
line: &'a str,
|
||||
wrap_width: Pixels,
|
||||
) -> impl Iterator<Item = Boundary> + 'a {
|
||||
let mut width = px(0.);
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut indent = None;
|
||||
let mut last_candidate_ix = 0;
|
||||
let mut last_candidate_width = px(0.);
|
||||
let mut last_wrap_ix = 0;
|
||||
let mut prev_c = '\0';
|
||||
let mut char_indices = line.char_indices();
|
||||
iter::from_fn(move || {
|
||||
for (ix, c) in char_indices.by_ref() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let char_width = self.width_for_char(c);
|
||||
width += char_width;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
|
||||
{
|
||||
indent = Some(
|
||||
Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
|
||||
);
|
||||
}
|
||||
|
||||
if last_candidate_ix > 0 {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
width -= last_candidate_width;
|
||||
last_candidate_ix = 0;
|
||||
} else {
|
||||
last_wrap_ix = ix;
|
||||
width = char_width;
|
||||
}
|
||||
|
||||
if let Some(indent) = indent {
|
||||
width += self.width_for_char(' ') * indent as f32;
|
||||
}
|
||||
|
||||
return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wrap_shaped_line<'a>(
|
||||
&'a mut self,
|
||||
str: &'a str,
|
||||
line: &'a Line,
|
||||
wrap_width: Pixels,
|
||||
) -> impl Iterator<Item = ShapedBoundary> + 'a {
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut last_candidate_ix = None;
|
||||
let mut last_candidate_x = px(0.);
|
||||
let mut last_wrap_ix = ShapedBoundary {
|
||||
run_ix: 0,
|
||||
glyph_ix: 0,
|
||||
};
|
||||
let mut last_wrap_x = px(0.);
|
||||
let mut prev_c = '\0';
|
||||
let mut glyphs = line
|
||||
.runs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(run_ix, run)| {
|
||||
run.glyphs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(glyph_ix, glyph)| {
|
||||
let character = str[glyph.index..].chars().next().unwrap();
|
||||
(
|
||||
ShapedBoundary { run_ix, glyph_ix },
|
||||
character,
|
||||
glyph.position.x,
|
||||
)
|
||||
})
|
||||
})
|
||||
.peekable();
|
||||
|
||||
iter::from_fn(move || {
|
||||
while let Some((ix, c, x)) = glyphs.next() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = Some(ix);
|
||||
last_candidate_x = x;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
|
||||
let width = next_x - last_wrap_x;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let Some(last_candidate_ix) = last_candidate_ix.take() {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
last_wrap_x = last_candidate_x;
|
||||
} else {
|
||||
last_wrap_ix = ix;
|
||||
last_wrap_x = x;
|
||||
}
|
||||
|
||||
return Some(last_wrap_ix);
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn is_boundary(&self, prev: char, next: char) -> bool {
|
||||
(prev == ' ') && (next != ' ')
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn width_for_char(&mut self, c: char) -> Pixels {
|
||||
if (c as u32) < 128 {
|
||||
if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
|
||||
cached_width
|
||||
} else {
|
||||
let width = self.compute_width_for_char(c);
|
||||
self.cached_ascii_char_widths[c as usize] = Some(width);
|
||||
width
|
||||
}
|
||||
} else {
|
||||
if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
|
||||
*cached_width
|
||||
} else {
|
||||
let width = self.compute_width_for_char(c);
|
||||
self.cached_other_char_widths.insert(c, width);
|
||||
width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_width_for_char(&self, c: char) -> Pixels {
|
||||
self.platform_text_system
|
||||
.layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
|
||||
.width
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Boundary {
|
||||
pub ix: usize,
|
||||
pub next_indent: u32,
|
||||
}
|
||||
|
||||
impl Boundary {
|
||||
fn new(ix: usize, next_indent: u32) -> Self {
|
||||
Self { ix, next_indent }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{font, App, RunStyle};
|
||||
|
||||
#[test]
|
||||
fn test_wrap_line() {
|
||||
App::test().run(|cx| {
|
||||
let text_system = cx.text_system().clone();
|
||||
let mut wrapper = LineWrapper::new(
|
||||
text_system.font_id(&font("Courier")).unwrap(),
|
||||
px(16.),
|
||||
text_system.platform_text_system.clone(),
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line("aa bbb cccc ddddd eeee", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(12, 0),
|
||||
Boundary::new(18, 0)
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(4, 0),
|
||||
Boundary::new(11, 0),
|
||||
Boundary::new(18, 0)
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" aaaaaaa", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 5),
|
||||
Boundary::new(9, 5),
|
||||
Boundary::new(11, 5),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" ", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(14, 0),
|
||||
Boundary::new(21, 0)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" aaaaaaaaaaaaaa", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(14, 3),
|
||||
Boundary::new(18, 3),
|
||||
Boundary::new(22, 3),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// todo! repeat this test
|
||||
#[test]
|
||||
fn test_wrap_shaped_line() {
|
||||
App::test().run(|cx| {
|
||||
let text_system = cx.text_system().clone();
|
||||
|
||||
let normal = RunStyle {
|
||||
font: font("Helvetica"),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
let bold = RunStyle {
|
||||
font: font("Helvetica").bold(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
|
||||
let text = "aa bbb cccc ddddd eeee";
|
||||
let line = text_system
|
||||
.layout_line(
|
||||
text,
|
||||
px(16.),
|
||||
&[
|
||||
(4, normal.clone()),
|
||||
(5, bold.clone()),
|
||||
(6, normal.clone()),
|
||||
(1, bold.clone()),
|
||||
(7, normal.clone()),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut wrapper = LineWrapper::new(
|
||||
text_system.font_id(&normal.font).unwrap(),
|
||||
px(16.),
|
||||
text_system.platform_text_system.clone(),
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_shaped_line(text, &line, px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
ShapedBoundary {
|
||||
run_ix: 1,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 2,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 4,
|
||||
glyph_ix: 2
|
||||
}
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
478
crates/gpui3/src/text_system/text_layout_cache.rs
Normal file
478
crates/gpui3/src/text_system/text_layout_cache.rs
Normal file
@ -0,0 +1,478 @@
|
||||
use crate::{
|
||||
black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
|
||||
Run, RunStyle, UnderlineStyle, WindowContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub(crate) struct TextLayoutCache {
|
||||
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
||||
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
}
|
||||
|
||||
impl TextLayoutCache {
|
||||
pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
Self {
|
||||
prev_frame: Mutex::new(HashMap::new()),
|
||||
curr_frame: RwLock::new(HashMap::new()),
|
||||
platform_text_system: fonts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_frame(&self) {
|
||||
let mut prev_frame = self.prev_frame.lock();
|
||||
let mut curr_frame = self.curr_frame.write();
|
||||
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
||||
curr_frame.clear();
|
||||
}
|
||||
|
||||
pub fn layout_line<'a>(
|
||||
&'a self,
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &[(usize, FontId)],
|
||||
) -> Arc<LineLayout> {
|
||||
dbg!("layout line");
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
font_size,
|
||||
runs,
|
||||
} as &dyn CacheKey;
|
||||
let curr_frame = self.curr_frame.upgradable_read();
|
||||
if let Some(layout) = curr_frame.get(key) {
|
||||
return layout.clone();
|
||||
}
|
||||
|
||||
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
|
||||
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
|
||||
curr_frame.insert(key, layout.clone());
|
||||
layout
|
||||
} else {
|
||||
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
|
||||
let key = CacheKeyValue {
|
||||
text: text.into(),
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
};
|
||||
curr_frame.insert(key, layout.clone());
|
||||
layout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait CacheKey {
|
||||
fn key(&self) -> CacheKeyRef;
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for (dyn CacheKey + 'a) {
|
||||
fn eq(&self, other: &dyn CacheKey) -> bool {
|
||||
self.key() == other.key()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eq for (dyn CacheKey + 'a) {}
|
||||
|
||||
impl<'a> Hash for (dyn CacheKey + 'a) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
struct CacheKeyValue {
|
||||
text: String,
|
||||
font_size: Pixels,
|
||||
runs: SmallVec<[(usize, FontId); 1]>,
|
||||
}
|
||||
|
||||
impl CacheKey for CacheKeyValue {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
CacheKeyRef {
|
||||
text: self.text.as_str(),
|
||||
font_size: self.font_size,
|
||||
runs: self.runs.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CacheKeyValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key().eq(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CacheKeyValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
|
||||
fn borrow(&self) -> &(dyn CacheKey + 'a) {
|
||||
self as &dyn CacheKey
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
struct CacheKeyRef<'a> {
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &'a [(usize, FontId)],
|
||||
}
|
||||
|
||||
impl<'a> CacheKey for CacheKeyRef<'a> {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for CacheKeyRef<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.text.hash(state);
|
||||
self.font_size.hash(state);
|
||||
for (len, font_id) in self.runs {
|
||||
len.hash(state);
|
||||
font_id.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Line {
|
||||
layout: Arc<LineLayout>,
|
||||
style_runs: SmallVec<[StyleRun; 32]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StyleRun {
|
||||
len: u32,
|
||||
color: Hsla,
|
||||
underline: UnderlineStyle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ShapedBoundary {
|
||||
pub run_ix: usize,
|
||||
pub glyph_ix: usize,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
|
||||
let mut style_runs = SmallVec::new();
|
||||
for (len, style) in runs {
|
||||
style_runs.push(StyleRun {
|
||||
len: *len as u32,
|
||||
color: style.color,
|
||||
underline: style.underline.clone().unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
Self { layout, style_runs }
|
||||
}
|
||||
|
||||
pub fn runs(&self) -> &[Run] {
|
||||
&self.layout.runs
|
||||
}
|
||||
|
||||
pub fn width(&self) -> Pixels {
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> Pixels {
|
||||
self.layout.font_size
|
||||
}
|
||||
|
||||
pub fn x_for_index(&self, index: usize) -> Pixels {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return glyph.position.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return Some(run.font_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.layout.len
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.layout.len == 0
|
||||
}
|
||||
|
||||
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
|
||||
if x >= self.layout.width {
|
||||
None
|
||||
} else {
|
||||
for run in self.layout.runs.iter().rev() {
|
||||
for glyph in run.glyphs.iter().rev() {
|
||||
if glyph.position.x <= x {
|
||||
return Some(glyph.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
|
||||
// todo!
|
||||
pub fn paint(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
visible_bounds: Bounds<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
||||
|
||||
let mut style_runs = self.style_runs.iter();
|
||||
let mut run_end = 0;
|
||||
let mut color = black();
|
||||
let mut underline = None;
|
||||
|
||||
for run in &self.layout.runs {
|
||||
cx.text_system().with_font(run.font_id, |system, font| {
|
||||
let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width;
|
||||
|
||||
for glyph in &run.glyphs {
|
||||
let glyph_origin = origin + baseline_offset + glyph.position;
|
||||
if glyph_origin.x > visible_bounds.upper_right().x {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
if glyph.index >= run_end {
|
||||
if let Some(style_run) = style_runs.next() {
|
||||
if let Some((_, underline_style)) = &mut underline {
|
||||
if style_run.underline != *underline_style {
|
||||
finished_underline = underline.take();
|
||||
}
|
||||
}
|
||||
if style_run.underline.thickness > px(0.) {
|
||||
underline.get_or_insert((
|
||||
point(
|
||||
glyph_origin.x,
|
||||
origin.y
|
||||
+ baseline_offset.y
|
||||
+ (self.layout.descent * 0.618),
|
||||
),
|
||||
UnderlineStyle {
|
||||
color: style_run.underline.color,
|
||||
thickness: style_run.underline.thickness,
|
||||
squiggly: style_run.underline.squiggly,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
run_end += style_run.len as usize;
|
||||
color = style_run.color;
|
||||
} else {
|
||||
run_end = self.layout.len;
|
||||
finished_underline = underline.take();
|
||||
}
|
||||
}
|
||||
|
||||
if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((_underline_origin, _underline_style)) = finished_underline {
|
||||
// cx.scene().insert(Underline {
|
||||
// origin: underline_origin,
|
||||
// width: glyph_origin.x - underline_origin.x,
|
||||
// thickness: underline_style.thickness.into(),
|
||||
// color: underline_style.color.unwrap(),
|
||||
// squiggly: underline_style.squiggly,
|
||||
// });
|
||||
}
|
||||
|
||||
// if glyph.is_emoji {
|
||||
// cx.scene().push_image_glyph(scene::ImageGlyph {
|
||||
// font_id: run.font_id,
|
||||
// font_size: self.layout.font_size,
|
||||
// id: glyph.id,
|
||||
// origin: glyph_origin,
|
||||
// });
|
||||
// } else {
|
||||
// cx.scene().push_glyph(scene::Glyph {
|
||||
// font_id: run.font_id,
|
||||
// font_size: self.layout.font_size,
|
||||
// id: glyph.id,
|
||||
// origin: glyph_origin,
|
||||
// color,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
|
||||
if let Some((_underline_start, _underline_style)) = underline.take() {
|
||||
let _line_end_x = origin.x + self.layout.width;
|
||||
// cx.scene().push_underline(Underline {
|
||||
// origin: underline_start,
|
||||
// width: line_end_x - underline_start.x,
|
||||
// color: underline_style.color,
|
||||
// thickness: underline_style.thickness.into(),
|
||||
// squiggly: underline_style.squiggly,
|
||||
// });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn paint_wrapped(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
_visible_bounds: Bounds<Pixels>,
|
||||
line_height: Pixels,
|
||||
boundaries: &[ShapedBoundary],
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
||||
|
||||
let mut boundaries = boundaries.into_iter().peekable();
|
||||
let mut color_runs = self.style_runs.iter();
|
||||
let mut style_run_end = 0;
|
||||
let mut _color = black(); // todo!
|
||||
let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
|
||||
let mut glyph_origin = origin;
|
||||
let mut prev_position = px(0.);
|
||||
for (run_ix, run) in self.layout.runs.iter().enumerate() {
|
||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||
glyph_origin.x += glyph.position.x - prev_position;
|
||||
|
||||
if boundaries
|
||||
.peek()
|
||||
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
|
||||
{
|
||||
boundaries.next();
|
||||
if let Some((_underline_origin, _underline_style)) = underline.take() {
|
||||
// cx.scene().push_underline(Underline {
|
||||
// origin: underline_origin,
|
||||
// width: glyph_origin.x - underline_origin.x,
|
||||
// thickness: underline_style.thickness.into(),
|
||||
// color: underline_style.color.unwrap(),
|
||||
// squiggly: underline_style.squiggly,
|
||||
// });
|
||||
}
|
||||
|
||||
glyph_origin = point(origin.x, glyph_origin.y + line_height);
|
||||
}
|
||||
prev_position = glyph.position.x;
|
||||
|
||||
let mut finished_underline = None;
|
||||
if glyph.index >= style_run_end {
|
||||
if let Some(style_run) = color_runs.next() {
|
||||
style_run_end += style_run.len as usize;
|
||||
_color = style_run.color;
|
||||
if let Some((_, underline_style)) = &mut underline {
|
||||
if style_run.underline != *underline_style {
|
||||
finished_underline = underline.take();
|
||||
}
|
||||
}
|
||||
if style_run.underline.thickness > px(0.) {
|
||||
underline.get_or_insert((
|
||||
glyph_origin
|
||||
+ point(
|
||||
px(0.),
|
||||
baseline_offset.y + (self.layout.descent * 0.618),
|
||||
),
|
||||
UnderlineStyle {
|
||||
color: Some(
|
||||
style_run.underline.color.unwrap_or(style_run.color),
|
||||
),
|
||||
thickness: style_run.underline.thickness,
|
||||
squiggly: style_run.underline.squiggly,
|
||||
},
|
||||
));
|
||||
}
|
||||
} else {
|
||||
style_run_end = self.layout.len;
|
||||
_color = black();
|
||||
finished_underline = underline.take();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_underline_origin, _underline_style)) = finished_underline {
|
||||
// cx.scene().push_underline(Underline {
|
||||
// origin: underline_origin,
|
||||
// width: glyph_origin.x - underline_origin.x,
|
||||
// thickness: underline_style.thickness.into(),
|
||||
// color: underline_style.color.unwrap(),
|
||||
// squiggly: underline_style.squiggly,
|
||||
// });
|
||||
}
|
||||
|
||||
cx.text_system().with_font(run.font_id, |system, font| {
|
||||
let _glyph_bounds = Bounds {
|
||||
origin: glyph_origin,
|
||||
size: system.bounding_box(font, self.layout.font_size)?.size,
|
||||
};
|
||||
// if glyph_bounds.intersects(visible_bounds) {
|
||||
// if glyph.is_emoji {
|
||||
// cx.scene().push_image_glyph(scene::ImageGlyph {
|
||||
// font_id: run.font_id,
|
||||
// font_size: self.layout.font_size,
|
||||
// id: glyph.id,
|
||||
// origin: glyph_bounds.origin() + baseline_offset,
|
||||
// });
|
||||
// } else {
|
||||
// cx.scene().push_glyph(scene::Glyph {
|
||||
// font_id: run.font_id,
|
||||
// font_size: self.layout.font_size,
|
||||
// id: glyph.id,
|
||||
// origin: glyph_bounds.origin() + baseline_offset,
|
||||
// color,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_underline_origin, _underline_style)) = underline.take() {
|
||||
// let line_end_x = glyph_origin.x + self.layout.width - prev_position;
|
||||
// cx.scene().push_underline(Underline {
|
||||
// origin: underline_origin,
|
||||
// width: line_end_x - underline_origin.x,
|
||||
// thickness: underline_style.thickness.into(),
|
||||
// color: underline_style.color,
|
||||
// squiggly: underline_style.squiggly,
|
||||
// });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Run {
|
||||
pub fn glyphs(&self) -> &[Glyph] {
|
||||
&self.glyphs
|
||||
}
|
||||
}
|
49
crates/gpui3/src/util.rs
Normal file
49
crates/gpui3/src/util.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use smol::future::FutureExt;
|
||||
use std::{future::Future, time::Duration};
|
||||
pub use util::*;
|
||||
|
||||
pub fn post_inc(value: &mut usize) -> usize {
|
||||
let prev = *value;
|
||||
*value += 1;
|
||||
prev
|
||||
}
|
||||
|
||||
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
{
|
||||
let timer = async {
|
||||
smol::Timer::after(timeout).await;
|
||||
Err(())
|
||||
};
|
||||
let future = async move { Ok(f.await) };
|
||||
timer.race(future).await
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use backtrace::{BacktraceFmt, BytesOrWideString};
|
||||
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let cwd = cwd.parent().unwrap();
|
||||
let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
|
||||
std::fmt::Display::fmt(&path, fmt)
|
||||
};
|
||||
let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
|
||||
for frame in self.0.frames() {
|
||||
let mut formatted_frame = fmt.frame();
|
||||
if frame
|
||||
.symbols()
|
||||
.iter()
|
||||
.any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
|
||||
{
|
||||
formatted_frame.backtrace_frame(frame)?;
|
||||
}
|
||||
}
|
||||
fmt.finish()
|
||||
}
|
||||
}
|
143
crates/gpui3/src/view.rs
Normal file
143
crates/gpui3/src/view.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use std::{any::Any, marker::PhantomData, sync::Arc};
|
||||
|
||||
pub struct View<S: Send + Sync, P> {
|
||||
state: Handle<S>,
|
||||
render: Arc<dyn Fn(&mut S, &mut ViewContext<S>) -> AnyElement<S> + Send + Sync + 'static>,
|
||||
parent_state_type: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync, P: 'static + Send> View<S, P> {
|
||||
pub fn into_any(self) -> AnyView<P> {
|
||||
AnyView {
|
||||
view: Arc::new(Mutex::new(self)),
|
||||
parent_state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Send + Sync, P> Clone for View<S, P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.clone(),
|
||||
render: self.render.clone(),
|
||||
parent_state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type RootView<S> = View<S, ()>;
|
||||
|
||||
pub fn view<S, P, E>(
|
||||
state: Handle<S>,
|
||||
render: impl Fn(&mut S, &mut ViewContext<S>) -> E + Send + Sync + 'static,
|
||||
) -> View<S, P>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
P: 'static,
|
||||
E: Element<State = S>,
|
||||
{
|
||||
View {
|
||||
state,
|
||||
render: Arc::new(move |state, cx| render(state, cx).into_any()),
|
||||
parent_state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Send + Sync + 'static, P: Send + 'static> Element for View<S, P> {
|
||||
type State = P;
|
||||
type FrameState = AnyElement<S>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let mut element = (self.render)(state, cx);
|
||||
let layout_id = element.layout(state, cx)?;
|
||||
Ok((layout_id, element))
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Layout,
|
||||
_: &mut Self::State,
|
||||
element: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
self.state
|
||||
.update(cx, |state, cx| element.paint(state, None, cx))
|
||||
}
|
||||
}
|
||||
|
||||
trait ViewObject: Send + 'static {
|
||||
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
element: &mut dyn Any,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<S: Send + Sync + 'static, P: Send + 'static> ViewObject for View<S, P> {
|
||||
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let mut element = (self.render)(state, cx);
|
||||
let layout_id = element.layout(state, cx)?;
|
||||
let element = Box::new(element) as Box<dyn Any>;
|
||||
Ok((layout_id, element))
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Layout, element: &mut dyn Any, cx: &mut WindowContext) -> Result<()> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let element = element.downcast_mut::<AnyElement<S>>().unwrap();
|
||||
element.paint(state, None, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyView<S> {
|
||||
view: Arc<Mutex<dyn ViewObject>>,
|
||||
parent_state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for AnyView<S> {
|
||||
type State = ();
|
||||
type FrameState = Box<dyn Any>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
self.view.lock().layout(cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
_: &mut (),
|
||||
element: &mut Box<dyn Any>,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
self.view.lock().paint(layout, element.as_mut(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for AnyView<S> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
view: self.view.clone(),
|
||||
parent_state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
403
crates/gpui3/src/window.rs
Normal file
403
crates/gpui3/src/window.rs
Normal file
@ -0,0 +1,403 @@
|
||||
use crate::{
|
||||
px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
|
||||
LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
|
||||
StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use futures::Future;
|
||||
use std::{any::TypeId, marker::PhantomData, mem, sync::Arc};
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct AnyWindow {}
|
||||
|
||||
pub struct Window {
|
||||
handle: AnyWindowHandle,
|
||||
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
||||
rem_size: Pixels,
|
||||
content_size: Size<Pixels>,
|
||||
layout_engine: TaffyLayoutEngine,
|
||||
pub(crate) root_view: Option<AnyView<()>>,
|
||||
mouse_position: Point<Pixels>,
|
||||
pub(crate) scene: Scene,
|
||||
pub(crate) dirty: bool,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
cx: &mut MainThread<AppContext>,
|
||||
) -> Self {
|
||||
let platform_window = cx.platform().open_window(handle, options);
|
||||
let mouse_position = platform_window.mouse_position();
|
||||
let content_size = platform_window.content_size();
|
||||
let scale_factor = platform_window.scale_factor();
|
||||
platform_window.on_resize(Box::new({
|
||||
let handle = handle;
|
||||
let cx = cx.to_async();
|
||||
move |content_size, scale_factor| {
|
||||
cx.update_window(handle, |cx| {
|
||||
cx.window.scene = Scene::new(scale_factor);
|
||||
cx.window.content_size = content_size;
|
||||
cx.window.dirty = true;
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
|
||||
let platform_window =
|
||||
MainThreadOnly::new(Arc::new(platform_window), cx.platform().dispatcher());
|
||||
|
||||
Window {
|
||||
handle,
|
||||
platform_window,
|
||||
rem_size: px(16.),
|
||||
content_size,
|
||||
layout_engine: TaffyLayoutEngine::new(),
|
||||
root_view: None,
|
||||
mouse_position,
|
||||
scene: Scene::new(scale_factor),
|
||||
dirty: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowContext<'a, 'w> {
|
||||
app: Reference<'a, AppContext>,
|
||||
window: Reference<'w, Window>,
|
||||
}
|
||||
|
||||
impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self {
|
||||
Self {
|
||||
app: Reference::Mutable(app),
|
||||
window: Reference::Mutable(window),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
self.window.dirty = true;
|
||||
}
|
||||
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
children: impl IntoIterator<Item = LayoutId>,
|
||||
) -> Result<LayoutId> {
|
||||
self.app.layout_id_buffer.clear();
|
||||
self.app.layout_id_buffer.extend(children.into_iter());
|
||||
let rem_size = self.rem_size();
|
||||
|
||||
self.window
|
||||
.layout_engine
|
||||
.request_layout(style, rem_size, &self.app.layout_id_buffer)
|
||||
}
|
||||
|
||||
pub fn request_measured_layout<
|
||||
F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
|
||||
>(
|
||||
&mut self,
|
||||
style: Style,
|
||||
rem_size: Pixels,
|
||||
measure: F,
|
||||
) -> Result<LayoutId> {
|
||||
self.window
|
||||
.layout_engine
|
||||
.request_measured_layout(style, rem_size, measure)
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
|
||||
Ok(self
|
||||
.window
|
||||
.layout_engine
|
||||
.layout(layout_id)
|
||||
.map(Into::into)?)
|
||||
}
|
||||
|
||||
pub fn rem_size(&self) -> Pixels {
|
||||
self.window.rem_size
|
||||
}
|
||||
|
||||
pub fn mouse_position(&self) -> Point<Pixels> {
|
||||
self.window.mouse_position
|
||||
}
|
||||
|
||||
pub fn scene(&mut self) -> &mut Scene {
|
||||
&mut self.window.scene
|
||||
}
|
||||
|
||||
pub fn run_on_main<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,
|
||||
) -> impl Future<Output = Result<R>>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
let id = self.window.handle.id;
|
||||
self.app.run_on_main(move |cx| {
|
||||
cx.update_window(id, |cx| {
|
||||
f(unsafe {
|
||||
mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn draw(&mut self) -> Result<()> {
|
||||
let unit_entity = self.unit_entity.clone();
|
||||
self.update_entity(&unit_entity, |_, cx| {
|
||||
let mut root_view = cx.window.root_view.take().unwrap();
|
||||
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
|
||||
let available_space = cx.window.content_size.map(Into::into);
|
||||
|
||||
dbg!("computing layout");
|
||||
cx.window
|
||||
.layout_engine
|
||||
.compute_layout(root_layout_id, available_space)?;
|
||||
dbg!("asking for layout");
|
||||
let layout = cx.window.layout_engine.layout(root_layout_id)?;
|
||||
|
||||
dbg!("painting root view");
|
||||
|
||||
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
|
||||
cx.window.root_view = Some(root_view);
|
||||
let scene = cx.window.scene.take();
|
||||
|
||||
let _ = cx.run_on_main(|cx| {
|
||||
cx.window
|
||||
.platform_window
|
||||
.borrow_on_main_thread()
|
||||
.draw(scene);
|
||||
});
|
||||
|
||||
cx.window.dirty = false;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MainThread<WindowContext<'_, '_>> {
|
||||
// todo!("implement other methods that use platform window")
|
||||
fn platform_window(&self) -> &dyn PlatformWindow {
|
||||
self.window.platform_window.borrow_on_main_thread().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for WindowContext<'_, '_> {
|
||||
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
|
||||
type Result<T> = T;
|
||||
|
||||
fn entity<T: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Handle<T> {
|
||||
let slot = self.app.entities.reserve();
|
||||
let entity = build_entity(&mut ViewContext::mutable(
|
||||
&mut *self.app,
|
||||
&mut self.window,
|
||||
slot.id,
|
||||
));
|
||||
self.entities.redeem(slot, entity)
|
||||
}
|
||||
|
||||
fn update_entity<T: Send + Sync + 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> R {
|
||||
let mut entity = self.entities.lease(handle);
|
||||
let result = update(
|
||||
&mut *entity,
|
||||
&mut ViewContext::mutable(&mut *self.app, &mut *self.window, handle.id),
|
||||
);
|
||||
self.entities.end_lease(entity);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> {
|
||||
type Target = AppContext;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.app
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.app
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StackContext for ViewContext<'_, '_, S> {
|
||||
fn app(&mut self) -> &mut AppContext {
|
||||
&mut *self.window_cx.app
|
||||
}
|
||||
|
||||
fn with_text_style<F, R>(&mut self, style: crate::TextStyleRefinement, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.window_cx.app.push_text_style(style);
|
||||
let result = f(self);
|
||||
self.window_cx.app.pop_text_style();
|
||||
result
|
||||
}
|
||||
|
||||
fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.window_cx.app.push_state(state);
|
||||
let result = f(self);
|
||||
self.window_cx.app.pop_state::<T>();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewContext<'a, 'w, S> {
|
||||
window_cx: WindowContext<'a, 'w>,
|
||||
entity_type: PhantomData<S>,
|
||||
entity_id: EntityId,
|
||||
}
|
||||
|
||||
impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
|
||||
fn mutable(app: &'a mut AppContext, window: &'w mut Window, entity_id: EntityId) -> Self {
|
||||
Self {
|
||||
window_cx: WindowContext::mutable(app, window),
|
||||
entity_id,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> WeakHandle<S> {
|
||||
self.entities.weak_handle(self.entity_id)
|
||||
}
|
||||
|
||||
pub fn observe<E: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
handle: &Handle<E>,
|
||||
on_notify: impl Fn(&mut S, Handle<E>, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static,
|
||||
) {
|
||||
let this = self.handle();
|
||||
let handle = handle.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app
|
||||
.observers
|
||||
.entry(handle.id)
|
||||
.or_default()
|
||||
.push(Arc::new(move |cx| {
|
||||
cx.update_window(window_handle.id, |cx| {
|
||||
if let Some(handle) = handle.upgrade(cx) {
|
||||
this.update(cx, |this, cx| on_notify(this, handle, cx))
|
||||
.is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
self.window_cx.notify();
|
||||
self.window_cx
|
||||
.app
|
||||
.pending_effects
|
||||
.push_back(Effect::Notify(self.entity_id));
|
||||
}
|
||||
|
||||
pub(crate) fn erase_state<R>(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R {
|
||||
let entity_id = self.unit_entity.id;
|
||||
let mut cx = ViewContext::mutable(
|
||||
&mut *self.window_cx.app,
|
||||
&mut *self.window_cx.window,
|
||||
entity_id,
|
||||
);
|
||||
f(&mut cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'w, S> Context for ViewContext<'a, 'w, S> {
|
||||
type EntityContext<'b, 'c, U: 'static + Send + Sync> = ViewContext<'b, 'c, U>;
|
||||
type Result<U> = U;
|
||||
|
||||
fn entity<T2: Send + Sync + 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T2>) -> T2,
|
||||
) -> Handle<T2> {
|
||||
self.window_cx.entity(build_entity)
|
||||
}
|
||||
|
||||
fn update_entity<U: Send + Sync + 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<U>,
|
||||
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
|
||||
) -> R {
|
||||
self.window_cx.update_entity(handle, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'w, S: 'static> std::ops::Deref for ViewContext<'a, 'w, S> {
|
||||
type Target = WindowContext<'a, 'w>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.window_cx
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'w, S: 'static> std::ops::DerefMut for ViewContext<'a, 'w, S> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.window_cx
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
|
||||
slotmap::new_key_type! { pub struct WindowId; }
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct WindowHandle<S> {
|
||||
id: WindowId,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> Copy for WindowHandle<S> {}
|
||||
|
||||
impl<S> Clone for WindowHandle<S> {
|
||||
fn clone(&self) -> Self {
|
||||
WindowHandle {
|
||||
id: self.id,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> WindowHandle<S> {
|
||||
pub fn new(id: WindowId) -> Self {
|
||||
WindowHandle {
|
||||
id,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Into<AnyWindowHandle> for WindowHandle<S> {
|
||||
fn into(self) -> AnyWindowHandle {
|
||||
AnyWindowHandle {
|
||||
id: self.id,
|
||||
state_type: TypeId::of::<S>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct AnyWindowHandle {
|
||||
pub(crate) id: WindowId,
|
||||
state_type: TypeId,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Layout {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<Pixels>,
|
||||
}
|
14
crates/gpui3_macros/Cargo.toml
Normal file
14
crates/gpui3_macros/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "gpui3_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/gpui3_macros.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0.72"
|
||||
quote = "1.0.9"
|
||||
proc-macro2 = "1.0.66"
|
50
crates/gpui3_macros/src/derive_element.rs
Normal file
50
crates/gpui3_macros/src/derive_element.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput, GenericParam};
|
||||
|
||||
pub fn derive_element(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let type_name = ast.ident;
|
||||
|
||||
let mut state_type = quote! { () };
|
||||
|
||||
for param in &ast.generics.params {
|
||||
if let GenericParam::Type(type_param) = param {
|
||||
let type_ident = &type_param.ident;
|
||||
state_type = quote! {#type_ident};
|
||||
}
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let gen = quote! {
|
||||
impl #impl_generics gpui3::Element for #type_name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
type State = #state_type;
|
||||
type FrameState = gpui3::AnyElement<#state_type>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut #state_type,
|
||||
cx: &mut gpui3::ViewContext<V>,
|
||||
) -> anyhow::Result<(gpui3::LayoutId, Self::FrameState)> {
|
||||
let mut rendered_element = self.render(state, cx).into_element().into_any();
|
||||
let layout_id = rendered_element.layout(state, cx)?;
|
||||
Ok((layout_id, rendered_element))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: &gpui3::Layout,
|
||||
state: &mut #state_type,
|
||||
rendered_element: &mut Self::FrameState,
|
||||
cx: &mut gpui3::ViewContext<V>,
|
||||
) {
|
||||
rendered_element.paint(layout.origin, state, cx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
14
crates/gpui3_macros/src/gpui3_macros.rs
Normal file
14
crates/gpui3_macros/src/gpui3_macros.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod derive_element;
|
||||
mod style_helpers;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn style_helpers(args: TokenStream) -> TokenStream {
|
||||
style_helpers::style_helpers(args)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Element, attributes(element_crate))]
|
||||
pub fn derive_element(input: TokenStream) -> TokenStream {
|
||||
derive_element::derive_element(input)
|
||||
}
|
326
crates/gpui3_macros/src/style_helpers.rs
Normal file
326
crates/gpui3_macros/src/style_helpers.rs
Normal file
@ -0,0 +1,326 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
};
|
||||
|
||||
struct StyleableMacroInput;
|
||||
|
||||
impl Parse for StyleableMacroInput {
|
||||
fn parse(_input: ParseStream) -> Result<Self> {
|
||||
Ok(StyleableMacroInput)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style_helpers(input: TokenStream) -> TokenStream {
|
||||
let _ = parse_macro_input!(input as StyleableMacroInput);
|
||||
let methods = generate_methods();
|
||||
let output = quote! {
|
||||
#(#methods)*
|
||||
};
|
||||
output.into()
|
||||
}
|
||||
|
||||
fn generate_methods() -> Vec<TokenStream2> {
|
||||
let mut methods = Vec::new();
|
||||
|
||||
for (prefix, auto_allowed, fields) in box_prefixes() {
|
||||
for (suffix, length_tokens) in box_suffixes() {
|
||||
if auto_allowed || suffix != "auto" {
|
||||
let method = generate_method(prefix, suffix, &fields, length_tokens);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (prefix, fields) in corner_prefixes() {
|
||||
for (suffix, radius_tokens) in corner_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, radius_tokens);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
|
||||
for (prefix, fields) in border_prefixes() {
|
||||
for (suffix, width_tokens) in border_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, width_tokens);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
|
||||
methods
|
||||
}
|
||||
|
||||
fn generate_method(
|
||||
prefix: &'static str,
|
||||
suffix: &'static str,
|
||||
fields: &Vec<TokenStream2>,
|
||||
length_tokens: TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let method_name = if suffix.is_empty() {
|
||||
format_ident!("{}", prefix)
|
||||
} else {
|
||||
format_ident!("{}_{}", prefix, suffix)
|
||||
};
|
||||
|
||||
let field_assignments = fields
|
||||
.iter()
|
||||
.map(|field_tokens| {
|
||||
quote! {
|
||||
style.#field_tokens = Some(gpui3::#length_tokens.into());
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let method = quote! {
|
||||
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
|
||||
let style = self.declared_style();
|
||||
#(#field_assignments)*
|
||||
self
|
||||
}
|
||||
};
|
||||
|
||||
method
|
||||
}
|
||||
|
||||
fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
|
||||
vec![
|
||||
("w", true, vec![quote! { size.width }]),
|
||||
("h", true, vec![quote! { size.height }]),
|
||||
(
|
||||
"size",
|
||||
true,
|
||||
vec![quote! {size.width}, quote! {size.height}],
|
||||
),
|
||||
("min_w", false, vec![quote! { min_size.width }]),
|
||||
("min_h", false, vec![quote! { min_size.height }]),
|
||||
("max_w", false, vec![quote! { max_size.width }]),
|
||||
("max_h", false, vec![quote! { max_size.height }]),
|
||||
(
|
||||
"m",
|
||||
true,
|
||||
vec![
|
||||
quote! { margin.top },
|
||||
quote! { margin.bottom },
|
||||
quote! { margin.left },
|
||||
quote! { margin.right },
|
||||
],
|
||||
),
|
||||
("mt", true, vec![quote! { margin.top }]),
|
||||
("mb", true, vec![quote! { margin.bottom }]),
|
||||
(
|
||||
"my",
|
||||
true,
|
||||
vec![quote! { margin.top }, quote! { margin.bottom }],
|
||||
),
|
||||
(
|
||||
"mx",
|
||||
true,
|
||||
vec![quote! { margin.left }, quote! { margin.right }],
|
||||
),
|
||||
("ml", true, vec![quote! { margin.left }]),
|
||||
("mr", true, vec![quote! { margin.right }]),
|
||||
(
|
||||
"p",
|
||||
false,
|
||||
vec![
|
||||
quote! { padding.top },
|
||||
quote! { padding.bottom },
|
||||
quote! { padding.left },
|
||||
quote! { padding.right },
|
||||
],
|
||||
),
|
||||
("pt", false, vec![quote! { padding.top }]),
|
||||
("pb", false, vec![quote! { padding.bottom }]),
|
||||
(
|
||||
"px",
|
||||
false,
|
||||
vec![quote! { padding.left }, quote! { padding.right }],
|
||||
),
|
||||
(
|
||||
"py",
|
||||
false,
|
||||
vec![quote! { padding.top }, quote! { padding.bottom }],
|
||||
),
|
||||
("pl", false, vec![quote! { padding.left }]),
|
||||
("pr", false, vec![quote! { padding.right }]),
|
||||
("top", true, vec![quote! { inset.top }]),
|
||||
("bottom", true, vec![quote! { inset.bottom }]),
|
||||
("left", true, vec![quote! { inset.left }]),
|
||||
("right", true, vec![quote! { inset.right }]),
|
||||
(
|
||||
"gap",
|
||||
false,
|
||||
vec![quote! { gap.width }, quote! { gap.height }],
|
||||
),
|
||||
("gap_x", false, vec![quote! { gap.width }]),
|
||||
("gap_y", false, vec![quote! { gap.height }]),
|
||||
]
|
||||
}
|
||||
|
||||
fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
vec![
|
||||
("0", quote! { px(0.) }),
|
||||
("0p5", quote! { rems(0.125) }),
|
||||
("1", quote! { rems(0.25) }),
|
||||
("1p5", quote! { rems(0.375) }),
|
||||
("2", quote! { rems(0.5) }),
|
||||
("2p5", quote! { rems(0.625) }),
|
||||
("3", quote! { rems(0.75) }),
|
||||
("3p5", quote! { rems(0.875) }),
|
||||
("4", quote! { rems(1.) }),
|
||||
("5", quote! { rems(1.25) }),
|
||||
("6", quote! { rems(1.5) }),
|
||||
("7", quote! { rems(1.75) }),
|
||||
("8", quote! { rems(2.0) }),
|
||||
("9", quote! { rems(2.25) }),
|
||||
("10", quote! { rems(2.5) }),
|
||||
("11", quote! { rems(2.75) }),
|
||||
("12", quote! { rems(3.) }),
|
||||
("16", quote! { rems(4.) }),
|
||||
("20", quote! { rems(5.) }),
|
||||
("24", quote! { rems(6.) }),
|
||||
("32", quote! { rems(8.) }),
|
||||
("40", quote! { rems(10.) }),
|
||||
("48", quote! { rems(12.) }),
|
||||
("56", quote! { rems(14.) }),
|
||||
("64", quote! { rems(16.) }),
|
||||
("72", quote! { rems(18.) }),
|
||||
("80", quote! { rems(20.) }),
|
||||
("96", quote! { rems(24.) }),
|
||||
("auto", quote! { auto() }),
|
||||
("px", quote! { px(1.) }),
|
||||
("full", quote! { relative(1.) }),
|
||||
("1_2", quote! { relative(0.5) }),
|
||||
("1_3", quote! { relative(1./3.) }),
|
||||
("2_3", quote! { relative(2./3.) }),
|
||||
("1_4", quote! { relative(0.25) }),
|
||||
("2_4", quote! { relative(0.5) }),
|
||||
("3_4", quote! { relative(0.75) }),
|
||||
("1_5", quote! { relative(0.2) }),
|
||||
("2_5", quote! { relative(0.4) }),
|
||||
("3_5", quote! { relative(0.6) }),
|
||||
("4_5", quote! { relative(0.8) }),
|
||||
("1_6", quote! { relative(1./6.) }),
|
||||
("5_6", quote! { relative(5./6.) }),
|
||||
("1_12", quote! { relative(1./12.) }),
|
||||
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
|
||||
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
|
||||
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
|
||||
]
|
||||
}
|
||||
|
||||
fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
|
||||
vec![
|
||||
(
|
||||
"rounded",
|
||||
vec![
|
||||
quote! { corner_radii.top_left },
|
||||
quote! { corner_radii.top_right },
|
||||
quote! { corner_radii.bottom_right },
|
||||
quote! { corner_radii.bottom_left },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_t",
|
||||
vec![
|
||||
quote! { corner_radii.top_left },
|
||||
quote! { corner_radii.top_right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_b",
|
||||
vec![
|
||||
quote! { corner_radii.bottom_left },
|
||||
quote! { corner_radii.bottom_right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_r",
|
||||
vec![
|
||||
quote! { corner_radii.top_right },
|
||||
quote! { corner_radii.bottom_right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_l",
|
||||
vec![
|
||||
quote! { corner_radii.top_left },
|
||||
quote! { corner_radii.bottom_left },
|
||||
],
|
||||
),
|
||||
("rounded_tl", vec![quote! { corner_radii.top_left }]),
|
||||
("rounded_tr", vec![quote! { corner_radii.top_right }]),
|
||||
("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
|
||||
("rounded_br", vec![quote! { corner_radii.bottom_right }]),
|
||||
]
|
||||
}
|
||||
|
||||
fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
vec![
|
||||
("none", quote! { px(0.) }),
|
||||
("sm", quote! { rems(0.125) }),
|
||||
("md", quote! { rems(0.25) }),
|
||||
("lg", quote! { rems(0.5) }),
|
||||
("xl", quote! { rems(0.75) }),
|
||||
("2xl", quote! { rems(1.) }),
|
||||
("3xl", quote! { rems(1.5) }),
|
||||
("full", quote! { px(9999.) }),
|
||||
]
|
||||
}
|
||||
|
||||
fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
|
||||
vec![
|
||||
(
|
||||
"border",
|
||||
vec![
|
||||
quote! { border_widths.top },
|
||||
quote! { border_widths.right },
|
||||
quote! { border_widths.bottom },
|
||||
quote! { border_widths.left },
|
||||
],
|
||||
),
|
||||
("border_t", vec![quote! { border_widths.top }]),
|
||||
("border_b", vec![quote! { border_widths.bottom }]),
|
||||
("border_r", vec![quote! { border_widths.right }]),
|
||||
("border_l", vec![quote! { border_widths.left }]),
|
||||
(
|
||||
"border_x",
|
||||
vec![
|
||||
quote! { border_widths.left },
|
||||
quote! { border_widths.right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"border_y",
|
||||
vec![
|
||||
quote! { border_widths.top },
|
||||
quote! { border_widths.bottom },
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
vec![
|
||||
("", quote! { px(1.) }),
|
||||
("0", quote! { px(0.) }),
|
||||
("1", quote! { px(1.) }),
|
||||
("2", quote! { px(2.) }),
|
||||
("3", quote! { px(3.) }),
|
||||
("4", quote! { px(4.) }),
|
||||
("5", quote! { px(5.) }),
|
||||
("6", quote! { px(6.) }),
|
||||
("7", quote! { px(7.) }),
|
||||
("8", quote! { px(8.) }),
|
||||
("9", quote! { px(9.) }),
|
||||
("10", quote! { px(10.) }),
|
||||
("11", quote! { px(11.) }),
|
||||
("12", quote! { px(12.) }),
|
||||
("16", quote! { px(16.) }),
|
||||
("20", quote! { px(20.) }),
|
||||
("24", quote! { px(24.) }),
|
||||
("32", quote! { px(32.) }),
|
||||
]
|
||||
}
|
@ -35,7 +35,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
let field_visibilities: Vec<_> = fields.iter().map(|f| &f.vis).collect();
|
||||
let wrapped_types: Vec<_> = fields.iter().map(|f| get_wrapper_type(f, &f.ty)).collect();
|
||||
|
||||
// Create trait bound that each wrapped type must implement Clone & Default
|
||||
// Create trait bound that each wrapped type must implement Clone // & Default
|
||||
let type_param_bounds: Vec<_> = wrapped_types
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
@ -51,13 +51,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
lifetimes: None,
|
||||
path: parse_quote!(Clone),
|
||||
}));
|
||||
punctuated.push_punct(syn::token::Add::default());
|
||||
punctuated.push_value(TypeParamBound::Trait(TraitBound {
|
||||
paren_token: None,
|
||||
modifier: syn::TraitBoundModifier::None,
|
||||
lifetimes: None,
|
||||
path: parse_quote!(Default),
|
||||
}));
|
||||
|
||||
// punctuated.push_punct(syn::token::Add::default());
|
||||
// punctuated.push_value(TypeParamBound::Trait(TraitBound {
|
||||
// paren_token: None,
|
||||
// modifier: syn::TraitBoundModifier::None,
|
||||
// lifetimes: None,
|
||||
// path: parse_quote!(Default),
|
||||
// }));
|
||||
punctuated
|
||||
},
|
||||
})
|
||||
@ -161,7 +162,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
};
|
||||
|
||||
let gen = quote! {
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct #refinement_ident #impl_generics {
|
||||
#( #field_visibilities #field_names: #wrapped_types ),*
|
||||
}
|
||||
@ -186,6 +187,16 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn default() -> Self {
|
||||
#refinement_ident {
|
||||
#( #field_names: Default::default() ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
|
@ -17,6 +17,12 @@ pub trait Refineable: Clone {
|
||||
{
|
||||
Self::default().refined(refinement)
|
||||
}
|
||||
fn from_cascade(cascade: &RefinementCascade<Self>) -> Self
|
||||
where
|
||||
Self: Default + Sized,
|
||||
{
|
||||
Self::default().refined(&cascade.merged())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefinementCascade<S: Refineable>(Vec<Option<S::Refinement>>);
|
||||
|
@ -8,15 +8,20 @@ publish = false
|
||||
name = "storybook"
|
||||
path = "src/storybook.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap = { version = "4.4", features = ["derive", "string"] }
|
||||
chrono = "0.4"
|
||||
derive_more.workspace = true
|
||||
fs = { path = "../fs" }
|
||||
futures.workspace = true
|
||||
gpui2 = { path = "../gpui2" }
|
||||
itertools = "0.11.0"
|
||||
log.workspace = true
|
||||
refineable = { path = "../refineable" }
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
|
5
crates/storybook/build.rs
Normal file
5
crates/storybook/build.rs
Normal file
@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
|
||||
// TODO: We shouldn't depend on WebRTC in editor
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
||||
}
|
2919
crates/storybook2/Cargo.lock
generated
Normal file
2919
crates/storybook2/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
crates/storybook2/Cargo.toml
Normal file
25
crates/storybook2/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "storybook2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "storybook"
|
||||
path = "src/storybook2.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
# TODO: Remove after diagnosing stack overflow.
|
||||
backtrace-on-stack-overflow = "0.3.0"
|
||||
gpui3 = { path = "../gpui3" }
|
||||
log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
simplelog = "0.9"
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui3 = { path = "../gpui3", features = ["test"] }
|
5
crates/storybook2/build.rs
Normal file
5
crates/storybook2/build.rs
Normal file
@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
|
||||
// TODO: We shouldn't depend on WebRTC in editor
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
||||
}
|
72
crates/storybook2/docs/thoughts.md
Normal file
72
crates/storybook2/docs/thoughts.md
Normal file
@ -0,0 +1,72 @@
|
||||
Much of element styling is now handled by an external engine.
|
||||
|
||||
|
||||
How do I make an element hover.
|
||||
|
||||
There's a hover style.
|
||||
|
||||
Hoverable needs to wrap another element. That element can be styled.
|
||||
|
||||
```rs
|
||||
struct Hoverable<E: Element> {
|
||||
|
||||
}
|
||||
|
||||
impl<V> Element<V> for Hoverable {
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
```rs
|
||||
#[derive(Styled, Interactive)]
|
||||
pub struct Div {
|
||||
declared_style: StyleRefinement,
|
||||
interactions: Interactions
|
||||
}
|
||||
|
||||
pub trait Styled {
|
||||
fn declared_style(&mut self) -> &mut StyleRefinement;
|
||||
fn compute_style(&mut self) -> Style {
|
||||
Style::default().refine(self.declared_style())
|
||||
}
|
||||
|
||||
// All the tailwind classes, modifying self.declared_style()
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn paint_background<V>(layout: Layout, cx: &mut PaintContext<V>);
|
||||
pub fn paint_foreground<V>(layout: Layout, cx: &mut PaintContext<V>);
|
||||
}
|
||||
|
||||
pub trait Interactive<V> {
|
||||
fn interactions(&mut self) -> &mut Interactions<V>;
|
||||
|
||||
fn on_click(self, )
|
||||
}
|
||||
|
||||
struct Interactions<V> {
|
||||
click: SmallVec<[<Rc<dyn Fn(&mut V, &dyn Any, )>; 1]>,
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
```rs
|
||||
|
||||
|
||||
trait Stylable {
|
||||
type Style;
|
||||
|
||||
fn with_style(self, style: Self::Style) -> Self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
176
crates/storybook2/src/collab_panel.rs
Normal file
176
crates/storybook2/src/collab_panel.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use crate::theme::{theme, Theme};
|
||||
use gpui3::{
|
||||
div, img, svg, view, AppContext, ArcCow, Context, Element, IntoAnyElement, ParentElement,
|
||||
ScrollState, StyleHelpers, View, ViewContext, WindowContext,
|
||||
};
|
||||
|
||||
pub struct CollabPanel {
|
||||
scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
pub fn collab_panel<S: 'static>(cx: &mut WindowContext) -> View<CollabPanel, S> {
|
||||
view(cx.entity(|cx| CollabPanel::new(cx)), CollabPanel::render)
|
||||
}
|
||||
|
||||
impl CollabPanel {
|
||||
fn new(_: &mut AppContext) -> Self {
|
||||
CollabPanel {
|
||||
scroll_state: ScrollState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CollabPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
|
||||
let theme = theme(cx);
|
||||
|
||||
// Panel
|
||||
div()
|
||||
.w_64()
|
||||
.h_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.text_color(theme.middle.base.default.foreground)
|
||||
.border_color(theme.middle.base.default.border)
|
||||
.border()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll(self.scroll_state.clone())
|
||||
// List Container
|
||||
.child(
|
||||
div()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.pb_1()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.border_b()
|
||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
||||
// .group()
|
||||
// List Section Header
|
||||
.child(self.list_section_header("#CRDB", true, theme))
|
||||
// List Item Large
|
||||
.child(self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
theme,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CHANNELS", true, theme)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CONTACTS", true, theme))
|
||||
.children(
|
||||
std::iter::repeat_with(|| {
|
||||
vec![
|
||||
self.list_item(
|
||||
"http://github.com/as-cii.png?s=50",
|
||||
"as-cii",
|
||||
theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/nathansobo.png?s=50",
|
||||
"nathansobo",
|
||||
theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
theme,
|
||||
),
|
||||
]
|
||||
})
|
||||
.take(10)
|
||||
.flatten(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.border_t()
|
||||
.border_color(theme.middle.variant.default.border)
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.middle.variant.default.foreground)
|
||||
.child("Find..."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_section_header(
|
||||
&self,
|
||||
label: impl IntoAnyElement<Self>,
|
||||
expanded: bool,
|
||||
theme: &Theme,
|
||||
) -> impl Element<State = Self> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(div().flex().gap_1().text_sm().child(label))
|
||||
.child(
|
||||
div().flex().h_full().gap_1().items_center().child(
|
||||
svg()
|
||||
.path(if expanded {
|
||||
"icons/radix/caret-down.svg"
|
||||
} else {
|
||||
"icons/radix/caret-up.svg"
|
||||
})
|
||||
.w_3p5()
|
||||
.h_3p5()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_item(
|
||||
&self,
|
||||
avatar_uri: impl Into<ArcCow<'static, str>>,
|
||||
label: impl IntoAnyElement<Self>,
|
||||
theme: &Theme,
|
||||
) -> impl Element<State = Self> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
// .hover()
|
||||
// .fill(theme.lowest.variant.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.variant.pressed.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
img()
|
||||
.uri(avatar_uri)
|
||||
.size_3p5()
|
||||
.rounded_full()
|
||||
.fill(theme.middle.positive.default.foreground),
|
||||
)
|
||||
.child(label),
|
||||
)
|
||||
}
|
||||
}
|
97
crates/storybook2/src/components.rs
Normal file
97
crates/storybook2/src/components.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use gpui2::{
|
||||
elements::div, interactive::Interactive, platform::MouseButton, style::StyleHelpers, ArcCow,
|
||||
Element, EventContext, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
use std::{marker::PhantomData, rc::Rc};
|
||||
|
||||
struct ButtonHandlers<V, D> {
|
||||
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
||||
}
|
||||
|
||||
impl<V, D> Default for ButtonHandlers<V, D> {
|
||||
fn default() -> Self {
|
||||
Self { click: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct Button<V: 'static, D: 'static> {
|
||||
handlers: ButtonHandlers<V, D>,
|
||||
label: Option<ArcCow<'static, str>>,
|
||||
icon: Option<ArcCow<'static, str>>,
|
||||
data: Rc<D>,
|
||||
view_type: PhantomData<V>,
|
||||
}
|
||||
|
||||
// Impl block for buttons without data.
|
||||
// See below for an impl block for any button.
|
||||
impl<V: 'static> Button<V, ()> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
handlers: ButtonHandlers::default(),
|
||||
label: None,
|
||||
icon: None,
|
||||
data: Rc::new(()),
|
||||
view_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
|
||||
Button {
|
||||
handlers: ButtonHandlers::default(),
|
||||
label: self.label,
|
||||
icon: self.icon,
|
||||
data: Rc::new(data),
|
||||
view_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Impl block for button regardless of its data type.
|
||||
impl<V: 'static, D: 'static> Button<V, D> {
|
||||
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.label = Some(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.icon = Some(icon.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers.click = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button<V>() -> Button<V, ()> {
|
||||
Button::new()
|
||||
}
|
||||
|
||||
impl<V: 'static, D: 'static> Button<V, D> {
|
||||
fn render(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> impl IntoElement<V> + Interactive<V> {
|
||||
// let colors = &cx.theme::<Theme>().colors;
|
||||
|
||||
let button = div()
|
||||
// .fill(colors.error(0.5))
|
||||
.h_4()
|
||||
.children(self.label.clone());
|
||||
|
||||
if let Some(handler) = self.handlers.click.clone() {
|
||||
let data = self.data.clone();
|
||||
button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
|
||||
handler(view, data.as_ref(), cx)
|
||||
})
|
||||
} else {
|
||||
button
|
||||
}
|
||||
}
|
||||
}
|
75
crates/storybook2/src/storybook2.rs
Normal file
75
crates/storybook2/src/storybook2.rs
Normal file
@ -0,0 +1,75 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
use gpui3::{Bounds, WindowBounds, WindowOptions};
|
||||
use log::LevelFilter;
|
||||
use simplelog::SimpleLogger;
|
||||
|
||||
mod collab_panel;
|
||||
mod theme;
|
||||
mod themes;
|
||||
mod workspace;
|
||||
|
||||
// gpui2::actions! {
|
||||
// storybook,
|
||||
// [ToggleInspector]
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
// unsafe { backtrace_on_stack_overflow::enable() };
|
||||
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
gpui3::App::production().run(|cx| {
|
||||
let window = cx.open_window(
|
||||
WindowOptions {
|
||||
bounds: WindowBounds::Fixed(Bounds {
|
||||
size: gpui3::Size {
|
||||
width: 800_f32.into(),
|
||||
height: 600_f32.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| workspace(cx),
|
||||
);
|
||||
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
use rust_embed::RustEmbed;
|
||||
use workspace::workspace;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "themes/**/*"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[include = "icons/**/*"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
|
||||
// impl AssetSource for Assets {
|
||||
// fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
||||
// Self::get(path)
|
||||
// .map(|f| f.data)
|
||||
// .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||
// }
|
||||
|
||||
// fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
|
||||
// Self::iter().filter(|p| p.starts_with(path)).collect()
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
|
||||
// let font_paths = Assets.list("fonts");
|
||||
// let mut embedded_fonts = Vec::new();
|
||||
// for font_path in &font_paths {
|
||||
// if font_path.ends_with(".ttf") {
|
||||
// let font_path = &*font_path;
|
||||
// let font_bytes = Assets.load(font_path).unwrap().to_vec();
|
||||
// embedded_fonts.push(Arc::from(font_bytes));
|
||||
// }
|
||||
// }
|
||||
// platform.fonts().add_fonts(&embedded_fonts).unwrap();
|
||||
// }
|
193
crates/storybook2/src/theme.rs
Normal file
193
crates/storybook2/src/theme.rs
Normal file
@ -0,0 +1,193 @@
|
||||
use gpui3::{Element, Hsla, Layout, LayoutId, Result, StackContext, ViewContext, WindowContext};
|
||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Theme {
|
||||
pub name: String,
|
||||
pub is_light: bool,
|
||||
pub lowest: Layer,
|
||||
pub middle: Layer,
|
||||
pub highest: Layer,
|
||||
pub popover_shadow: Shadow,
|
||||
pub modal_shadow: Shadow,
|
||||
#[serde(deserialize_with = "deserialize_player_colors")]
|
||||
pub players: Vec<PlayerColors>,
|
||||
#[serde(deserialize_with = "deserialize_syntax_colors")]
|
||||
pub syntax: HashMap<String, Hsla>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Layer {
|
||||
pub base: StyleSet,
|
||||
pub variant: StyleSet,
|
||||
pub on: StyleSet,
|
||||
pub accent: StyleSet,
|
||||
pub positive: StyleSet,
|
||||
pub warning: StyleSet,
|
||||
pub negative: StyleSet,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct StyleSet {
|
||||
#[serde(rename = "default")]
|
||||
pub default: ContainerColors,
|
||||
pub hovered: ContainerColors,
|
||||
pub pressed: ContainerColors,
|
||||
pub active: ContainerColors,
|
||||
pub disabled: ContainerColors,
|
||||
pub inverted: ContainerColors,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct ContainerColors {
|
||||
pub background: Hsla,
|
||||
pub foreground: Hsla,
|
||||
pub border: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct PlayerColors {
|
||||
pub selection: Hsla,
|
||||
pub cursor: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Shadow {
|
||||
pub blur: u8,
|
||||
pub color: Hsla,
|
||||
pub offset: Vec<u8>,
|
||||
}
|
||||
|
||||
fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct PlayerArrayVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PlayerArrayVisitor {
|
||||
type Value = Vec<PlayerColors>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("an object with integer keys")
|
||||
}
|
||||
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(
|
||||
self,
|
||||
mut map: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let mut players = Vec::with_capacity(8);
|
||||
while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
|
||||
if key < 8 {
|
||||
players.push(value);
|
||||
} else {
|
||||
return Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Unsigned(key as u64),
|
||||
&"a key in range 0..7",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(players)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(PlayerArrayVisitor)
|
||||
}
|
||||
|
||||
fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct ColorWrapper {
|
||||
color: Hsla,
|
||||
}
|
||||
|
||||
struct SyntaxVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SyntaxVisitor {
|
||||
type Value = HashMap<String, Hsla>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a map with keys and objects with a single color field as values")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
|
||||
where
|
||||
M: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut result = HashMap::new();
|
||||
while let Some(key) = map.next_key()? {
|
||||
let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
|
||||
result.insert(key, wrapper.color);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_map(SyntaxVisitor)
|
||||
}
|
||||
|
||||
pub fn themed<E, F>(theme: Theme, cx: &mut ViewContext<E::State>, build_child: F) -> Themed<E>
|
||||
where
|
||||
E: Element,
|
||||
F: FnOnce(&mut ViewContext<E::State>) -> E,
|
||||
{
|
||||
let child = cx.with_state(theme.clone(), |cx| build_child(cx));
|
||||
Themed { theme, child }
|
||||
}
|
||||
|
||||
pub struct Themed<E> {
|
||||
pub(crate) theme: Theme,
|
||||
pub(crate) child: E,
|
||||
}
|
||||
|
||||
impl<E: Element> Element for Themed<E> {
|
||||
type State = E::State;
|
||||
type FrameState = E::FrameState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut E::State,
|
||||
cx: &mut ViewContext<E::State>,
|
||||
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
cx.with_state(self.theme.clone(), |cx| self.child.layout(state, cx))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
state: &mut Self::State,
|
||||
frame_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
cx.with_state(self.theme.clone(), |cx| {
|
||||
self.child.paint(layout, state, frame_state, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
|
||||
// settings::get::<ThemeSettings>(cx)
|
||||
// .theme
|
||||
// .deserialized_base_theme
|
||||
// .lock()
|
||||
// .get_or_insert_with(|| {
|
||||
// let theme: Theme =
|
||||
// serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
||||
// .unwrap();
|
||||
// Box::new(theme)
|
||||
// })
|
||||
// .downcast_ref::<Theme>()
|
||||
// .unwrap()
|
||||
// .clone()
|
||||
// }
|
||||
|
||||
pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
|
||||
cx.state()
|
||||
}
|
3
crates/storybook2/src/themes.rs
Normal file
3
crates/storybook2/src/themes.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod rose_pine_dawn;
|
||||
|
||||
pub use rose_pine_dawn::*;
|
845
crates/storybook2/src/themes/rose_pine_dawn.rs
Normal file
845
crates/storybook2/src/themes/rose_pine_dawn.rs
Normal file
@ -0,0 +1,845 @@
|
||||
use crate::theme::Theme;
|
||||
use gpui3::serde_json::{self, json};
|
||||
|
||||
pub fn rose_pine_dawn() -> Theme {
|
||||
serde_json::from_value(json! {
|
||||
{
|
||||
"name": "Rosé Pine",
|
||||
"is_light": false,
|
||||
"ramps": {},
|
||||
"lowest": {
|
||||
"base": {
|
||||
"default": {
|
||||
"background": "#292739",
|
||||
"border": "#423f55",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#423f55",
|
||||
"border": "#423f55",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#4e4b63",
|
||||
"border": "#423f55",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"active": {
|
||||
"background": "#47445b",
|
||||
"border": "#36334a",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#292739",
|
||||
"border": "#353347",
|
||||
"foreground": "#2f2b43"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#4b4860"
|
||||
}
|
||||
},
|
||||
"variant": {
|
||||
"default": {
|
||||
"background": "#292739",
|
||||
"border": "#423f55",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#423f55",
|
||||
"border": "#423f55",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#4e4b63",
|
||||
"border": "#423f55",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"active": {
|
||||
"background": "#47445b",
|
||||
"border": "#36334a",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#292739",
|
||||
"border": "#353347",
|
||||
"foreground": "#2f2b43"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#4b4860"
|
||||
}
|
||||
},
|
||||
"on": {
|
||||
"default": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#232132",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2f2d40",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"active": {
|
||||
"background": "#403e53",
|
||||
"border": "#504d65",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#1e1c2c",
|
||||
"foreground": "#3b384f"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#3b394e"
|
||||
}
|
||||
},
|
||||
"accent": {
|
||||
"default": {
|
||||
"background": "#2f3739",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#435255",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#4e6164",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"active": {
|
||||
"background": "#5d757a",
|
||||
"border": "#6e8f94",
|
||||
"foreground": "#fbfdfd"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#2f3739",
|
||||
"border": "#3a4446",
|
||||
"foreground": "#85aeb5"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fbfdfd",
|
||||
"border": "#171717",
|
||||
"foreground": "#587074"
|
||||
}
|
||||
},
|
||||
"positive": {
|
||||
"default": {
|
||||
"background": "#182e23",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#254839",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2c5645",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"active": {
|
||||
"background": "#356b57",
|
||||
"border": "#40836c",
|
||||
"foreground": "#f9fdfb"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#182e23",
|
||||
"border": "#1e3b2e",
|
||||
"foreground": "#4ea287"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#f9fdfb",
|
||||
"border": "#000e00",
|
||||
"foreground": "#326552"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"default": {
|
||||
"background": "#50341a",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#6d4d2b",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#7e5a34",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"active": {
|
||||
"background": "#946e41",
|
||||
"border": "#b0854f",
|
||||
"foreground": "#fffcf9"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#50341a",
|
||||
"border": "#5e4023",
|
||||
"foreground": "#d2a263"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fffcf9",
|
||||
"border": "#2c1600",
|
||||
"foreground": "#8e683c"
|
||||
}
|
||||
},
|
||||
"negative": {
|
||||
"default": {
|
||||
"background": "#431820",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#612834",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#71303f",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"active": {
|
||||
"background": "#883c4f",
|
||||
"border": "#a44961",
|
||||
"foreground": "#fff9fa"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#431820",
|
||||
"border": "#52202a",
|
||||
"foreground": "#c75c79"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fff9fa",
|
||||
"border": "#230000",
|
||||
"foreground": "#82384a"
|
||||
}
|
||||
}
|
||||
},
|
||||
"middle": {
|
||||
"base": {
|
||||
"default": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#232132",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2f2d40",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"active": {
|
||||
"background": "#403e53",
|
||||
"border": "#504d65",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#1e1c2c",
|
||||
"foreground": "#3b384f"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#3b394e"
|
||||
}
|
||||
},
|
||||
"variant": {
|
||||
"default": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#232132",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#232132",
|
||||
"border": "#232132",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2f2d40",
|
||||
"border": "#232132",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"active": {
|
||||
"background": "#403e53",
|
||||
"border": "#504d65",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#1e1c2c",
|
||||
"foreground": "#3b384f"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#3b394e"
|
||||
}
|
||||
},
|
||||
"on": {
|
||||
"default": {
|
||||
"background": "#191724",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#1c1a29",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#1d1b2b",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"active": {
|
||||
"background": "#222031",
|
||||
"border": "#353347",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#191724",
|
||||
"border": "#1a1826",
|
||||
"foreground": "#4e4b63"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#1f1d2e"
|
||||
}
|
||||
},
|
||||
"accent": {
|
||||
"default": {
|
||||
"background": "#2f3739",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#435255",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#4e6164",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"active": {
|
||||
"background": "#5d757a",
|
||||
"border": "#6e8f94",
|
||||
"foreground": "#fbfdfd"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#2f3739",
|
||||
"border": "#3a4446",
|
||||
"foreground": "#85aeb5"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fbfdfd",
|
||||
"border": "#171717",
|
||||
"foreground": "#587074"
|
||||
}
|
||||
},
|
||||
"positive": {
|
||||
"default": {
|
||||
"background": "#182e23",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#254839",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2c5645",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"active": {
|
||||
"background": "#356b57",
|
||||
"border": "#40836c",
|
||||
"foreground": "#f9fdfb"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#182e23",
|
||||
"border": "#1e3b2e",
|
||||
"foreground": "#4ea287"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#f9fdfb",
|
||||
"border": "#000e00",
|
||||
"foreground": "#326552"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"default": {
|
||||
"background": "#50341a",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#6d4d2b",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#7e5a34",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"active": {
|
||||
"background": "#946e41",
|
||||
"border": "#b0854f",
|
||||
"foreground": "#fffcf9"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#50341a",
|
||||
"border": "#5e4023",
|
||||
"foreground": "#d2a263"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fffcf9",
|
||||
"border": "#2c1600",
|
||||
"foreground": "#8e683c"
|
||||
}
|
||||
},
|
||||
"negative": {
|
||||
"default": {
|
||||
"background": "#431820",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#612834",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#71303f",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"active": {
|
||||
"background": "#883c4f",
|
||||
"border": "#a44961",
|
||||
"foreground": "#fff9fa"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#431820",
|
||||
"border": "#52202a",
|
||||
"foreground": "#c75c79"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fff9fa",
|
||||
"border": "#230000",
|
||||
"foreground": "#82384a"
|
||||
}
|
||||
}
|
||||
},
|
||||
"highest": {
|
||||
"base": {
|
||||
"default": {
|
||||
"background": "#191724",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#1c1a29",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#1d1b2b",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"active": {
|
||||
"background": "#222031",
|
||||
"border": "#353347",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#191724",
|
||||
"border": "#1a1826",
|
||||
"foreground": "#4e4b63"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#1f1d2e"
|
||||
}
|
||||
},
|
||||
"variant": {
|
||||
"default": {
|
||||
"background": "#191724",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#1c1a29",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#1d1b2b",
|
||||
"border": "#1c1a29",
|
||||
"foreground": "#75718e"
|
||||
},
|
||||
"active": {
|
||||
"background": "#222031",
|
||||
"border": "#353347",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#191724",
|
||||
"border": "#1a1826",
|
||||
"foreground": "#4e4b63"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#1f1d2e"
|
||||
}
|
||||
},
|
||||
"on": {
|
||||
"default": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#232132",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2f2d40",
|
||||
"border": "#232132",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"active": {
|
||||
"background": "#403e53",
|
||||
"border": "#504d65",
|
||||
"foreground": "#e0def4"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#1d1b2a",
|
||||
"border": "#1e1c2c",
|
||||
"foreground": "#3b384f"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#e0def4",
|
||||
"border": "#191724",
|
||||
"foreground": "#3b394e"
|
||||
}
|
||||
},
|
||||
"accent": {
|
||||
"default": {
|
||||
"background": "#2f3739",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#435255",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#4e6164",
|
||||
"border": "#435255",
|
||||
"foreground": "#9cced7"
|
||||
},
|
||||
"active": {
|
||||
"background": "#5d757a",
|
||||
"border": "#6e8f94",
|
||||
"foreground": "#fbfdfd"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#2f3739",
|
||||
"border": "#3a4446",
|
||||
"foreground": "#85aeb5"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fbfdfd",
|
||||
"border": "#171717",
|
||||
"foreground": "#587074"
|
||||
}
|
||||
},
|
||||
"positive": {
|
||||
"default": {
|
||||
"background": "#182e23",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#254839",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#2c5645",
|
||||
"border": "#254839",
|
||||
"foreground": "#5dc2a3"
|
||||
},
|
||||
"active": {
|
||||
"background": "#356b57",
|
||||
"border": "#40836c",
|
||||
"foreground": "#f9fdfb"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#182e23",
|
||||
"border": "#1e3b2e",
|
||||
"foreground": "#4ea287"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#f9fdfb",
|
||||
"border": "#000e00",
|
||||
"foreground": "#326552"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"default": {
|
||||
"background": "#50341a",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#6d4d2b",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#7e5a34",
|
||||
"border": "#6d4d2b",
|
||||
"foreground": "#f5c177"
|
||||
},
|
||||
"active": {
|
||||
"background": "#946e41",
|
||||
"border": "#b0854f",
|
||||
"foreground": "#fffcf9"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#50341a",
|
||||
"border": "#5e4023",
|
||||
"foreground": "#d2a263"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fffcf9",
|
||||
"border": "#2c1600",
|
||||
"foreground": "#8e683c"
|
||||
}
|
||||
},
|
||||
"negative": {
|
||||
"default": {
|
||||
"background": "#431820",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"hovered": {
|
||||
"background": "#612834",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"pressed": {
|
||||
"background": "#71303f",
|
||||
"border": "#612834",
|
||||
"foreground": "#ea6f92"
|
||||
},
|
||||
"active": {
|
||||
"background": "#883c4f",
|
||||
"border": "#a44961",
|
||||
"foreground": "#fff9fa"
|
||||
},
|
||||
"disabled": {
|
||||
"background": "#431820",
|
||||
"border": "#52202a",
|
||||
"foreground": "#c75c79"
|
||||
},
|
||||
"inverted": {
|
||||
"background": "#fff9fa",
|
||||
"border": "#230000",
|
||||
"foreground": "#82384a"
|
||||
}
|
||||
}
|
||||
},
|
||||
"popover_shadow": {
|
||||
"blur": 4,
|
||||
"color": "#00000033",
|
||||
"offset": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"modal_shadow": {
|
||||
"blur": 16,
|
||||
"color": "#00000033",
|
||||
"offset": [
|
||||
0,
|
||||
2
|
||||
]
|
||||
},
|
||||
"players": {
|
||||
"0": {
|
||||
"selection": "#9cced73d",
|
||||
"cursor": "#9cced7"
|
||||
},
|
||||
"1": {
|
||||
"selection": "#5dc2a33d",
|
||||
"cursor": "#5dc2a3"
|
||||
},
|
||||
"2": {
|
||||
"selection": "#9d76913d",
|
||||
"cursor": "#9d7691"
|
||||
},
|
||||
"3": {
|
||||
"selection": "#c4a7e63d",
|
||||
"cursor": "#c4a7e6"
|
||||
},
|
||||
"4": {
|
||||
"selection": "#c4a7e63d",
|
||||
"cursor": "#c4a7e6"
|
||||
},
|
||||
"5": {
|
||||
"selection": "#32748f3d",
|
||||
"cursor": "#32748f"
|
||||
},
|
||||
"6": {
|
||||
"selection": "#ea6f923d",
|
||||
"cursor": "#ea6f92"
|
||||
},
|
||||
"7": {
|
||||
"selection": "#f5c1773d",
|
||||
"cursor": "#f5c177"
|
||||
}
|
||||
},
|
||||
"syntax": {
|
||||
"comment": {
|
||||
"color": "#6e6a86"
|
||||
},
|
||||
"operator": {
|
||||
"color": "#31748f"
|
||||
},
|
||||
"punctuation": {
|
||||
"color": "#908caa"
|
||||
},
|
||||
"variable": {
|
||||
"color": "#e0def4"
|
||||
},
|
||||
"string": {
|
||||
"color": "#f6c177"
|
||||
},
|
||||
"type": {
|
||||
"color": "#9ccfd8"
|
||||
},
|
||||
"type.builtin": {
|
||||
"color": "#9ccfd8"
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#ebbcba"
|
||||
},
|
||||
"function": {
|
||||
"color": "#ebbcba"
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#31748f"
|
||||
},
|
||||
"tag": {
|
||||
"color": "#9ccfd8"
|
||||
},
|
||||
"function.method": {
|
||||
"color": "#ebbcba"
|
||||
},
|
||||
"title": {
|
||||
"color": "#f6c177"
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#9ccfd8",
|
||||
"italic": false
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#ebbcba"
|
||||
}
|
||||
},
|
||||
"color_family": {
|
||||
"neutral": {
|
||||
"low": 11.568627450980392,
|
||||
"high": 91.37254901960785,
|
||||
"range": 79.80392156862746,
|
||||
"scaling_value": 1.2530712530712529
|
||||
},
|
||||
"red": {
|
||||
"low": 6.862745098039216,
|
||||
"high": 100,
|
||||
"range": 93.13725490196079,
|
||||
"scaling_value": 1.0736842105263158
|
||||
},
|
||||
"orange": {
|
||||
"low": 5.490196078431373,
|
||||
"high": 100,
|
||||
"range": 94.50980392156863,
|
||||
"scaling_value": 1.058091286307054
|
||||
},
|
||||
"yellow": {
|
||||
"low": 8.627450980392156,
|
||||
"high": 100,
|
||||
"range": 91.37254901960785,
|
||||
"scaling_value": 1.094420600858369
|
||||
},
|
||||
"green": {
|
||||
"low": 2.7450980392156863,
|
||||
"high": 100,
|
||||
"range": 97.25490196078431,
|
||||
"scaling_value": 1.028225806451613
|
||||
},
|
||||
"cyan": {
|
||||
"low": 0,
|
||||
"high": 100,
|
||||
"range": 100,
|
||||
"scaling_value": 1
|
||||
},
|
||||
"blue": {
|
||||
"low": 9.019607843137255,
|
||||
"high": 100,
|
||||
"range": 90.98039215686275,
|
||||
"scaling_value": 1.0991379310344827
|
||||
},
|
||||
"violet": {
|
||||
"low": 5.490196078431373,
|
||||
"high": 100,
|
||||
"range": 94.50980392156863,
|
||||
"scaling_value": 1.058091286307054
|
||||
},
|
||||
"magenta": {
|
||||
"low": 0,
|
||||
"high": 100,
|
||||
"range": 100,
|
||||
"scaling_value": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
473
crates/storybook2/src/workspace.rs
Normal file
473
crates/storybook2/src/workspace.rs
Normal file
@ -0,0 +1,473 @@
|
||||
use crate::{
|
||||
collab_panel::{collab_panel, CollabPanel},
|
||||
theme::theme,
|
||||
themes::rose_pine_dawn,
|
||||
};
|
||||
use gpui3::{
|
||||
div, img, svg, view, Context, Element, ParentElement, RootView, StyleHelpers, View,
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
|
||||
pub struct Workspace {
|
||||
left_panel: View<CollabPanel, Self>,
|
||||
right_panel: View<CollabPanel, Self>,
|
||||
}
|
||||
|
||||
pub fn workspace(cx: &mut WindowContext) -> RootView<Workspace> {
|
||||
view(cx.entity(|cx| Workspace::new(cx)), Workspace::render)
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
left_panel: collab_panel(cx),
|
||||
right_panel: collab_panel(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
|
||||
let theme = rose_pine_dawn();
|
||||
div()
|
||||
.font("Helvetica")
|
||||
.text_base()
|
||||
.size_full()
|
||||
.fill(theme.middle.positive.default.background)
|
||||
.child("Hello world")
|
||||
|
||||
// TODO: Implement style.
|
||||
//.size_full().fill(gpui3::hsla(0.83, 1., 0.5, 1.))
|
||||
|
||||
// TODO: Debug font not font.
|
||||
//.child("Is this thing on?")
|
||||
|
||||
// themed(rose_pine_dawn(), cx, |cx| {
|
||||
// div()
|
||||
// .size_full()
|
||||
// .flex()
|
||||
// .flex_col()
|
||||
// .font("Zed Sans Extended")
|
||||
// .gap_0()
|
||||
// .justify_start()
|
||||
// .items_start()
|
||||
// .text_color(theme.lowest.base.default.foreground)
|
||||
// // .fill(theme.middle.base.default.background)
|
||||
// .fill(gpui3::hsla(0.83, 1., 0.5, 1.))
|
||||
// .child(titlebar(cx))
|
||||
// .child(
|
||||
// div()
|
||||
// .flex_1()
|
||||
// .w_full()
|
||||
// .flex()
|
||||
// .flex_row()
|
||||
// .overflow_hidden()
|
||||
// .child(self.left_panel.clone())
|
||||
// .child(div().h_full().flex_1())
|
||||
// .child(self.right_panel.clone()),
|
||||
// )
|
||||
// .child(statusbar::statusbar(cx))
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
struct Titlebar;
|
||||
|
||||
pub fn titlebar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let ref mut this = Titlebar;
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(this.left_group(cx))
|
||||
.child(this.right_group(cx))
|
||||
}
|
||||
|
||||
impl Titlebar {
|
||||
fn render<V: 'static + Send + Sync>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> impl Element<State = V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(self.left_group(cx))
|
||||
.child(self.right_group(cx))
|
||||
}
|
||||
|
||||
fn left_group<S: 'static + Send + Sync>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Traffic Lights === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.positive.default.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.warning.default.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
),
|
||||
)
|
||||
// === Project Info === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(div().text_sm().child("project")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(div().text_sm().child("branch")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn right_group<S: 'static + Send + Sync>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_3()
|
||||
.px_2()
|
||||
// === Actions === //
|
||||
.child(
|
||||
div().child(
|
||||
div().flex().items_center().gap_1().child(
|
||||
div().size_4().flex().items_center().justify_center().child(
|
||||
svg()
|
||||
.path("icons/exit.svg")
|
||||
.size_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
// === Comms === //
|
||||
.child(
|
||||
div().child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/microphone.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/radix/speaker-loud.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/radix/desktop.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
// User Group
|
||||
.child(
|
||||
div().child(
|
||||
div()
|
||||
.px_1()
|
||||
.py_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.gap_0p5()
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
img()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size_4()
|
||||
.rounded_md()
|
||||
.fill(theme.middle.on.default.foreground),
|
||||
)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/caret_down_8.svg")
|
||||
.w_2()
|
||||
.h_2()
|
||||
.fill(theme.lowest.variant.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================ //
|
||||
|
||||
mod statusbar {
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn statusbar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
// .child(left_group(cx))
|
||||
// .child(right_group(cx))
|
||||
}
|
||||
|
||||
fn left_group<V: 'static + Send + Sync>(cx: &mut ViewContext<V>) -> impl Element<State = V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Tools === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/project.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/conversations.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/file_icons/notebook.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
// === Diagnostics === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.gap_0p5()
|
||||
.px_1()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
// .hover()
|
||||
// .fill(theme.lowest.base.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/error.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
)
|
||||
.child(div().text_sm().child("2")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.child("Something is wrong"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn right_group<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Tools === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/check_circle.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/copilot.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum ArcCow<'a, T: ?Sized> {
|
||||
@ -72,3 +75,12 @@ impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + Debug> Debug for ArcCow<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ArcCow::Borrowed(borrowed) => Debug::fmt(borrowed, f),
|
||||
ArcCow::Owned(owned) => Debug::fmt(&**owned, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +139,7 @@ tree-sitter-nu.workspace = true
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
owning_ref = "0.4.1"
|
||||
|
||||
[dev-dependencies]
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
|
Loading…
Reference in New Issue
Block a user