Merge branch 'gpui2' into marshall/gpui2-playground
This commit is contained in:
commit
e6c7e57711
39 changed files with 2605 additions and 1041 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -1070,20 +1070,6 @@ name = "bytemuck"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||||
dependencies = [
|
|
||||||
"bytemuck_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytemuck_derive"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.29",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
|
@ -3282,7 +3268,6 @@ dependencies = [
|
||||||
"bindgen 0.65.1",
|
"bindgen 0.65.1",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"block",
|
"block",
|
||||||
"bytemuck",
|
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"collections",
|
"collections",
|
||||||
|
|
|
@ -84,7 +84,6 @@ impl ImageCache {
|
||||||
let format = image::guess_format(&body)?;
|
let format = image::guess_format(&body)?;
|
||||||
let image =
|
let image =
|
||||||
image::load_from_memory_with_format(&body, format)?.into_bgra8();
|
image::load_from_memory_with_format(&body, format)?.into_bgra8();
|
||||||
|
|
||||||
Ok(ImageData::new(image))
|
Ok(ImageData::new(image))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ impl AtlasAllocator {
|
||||||
};
|
};
|
||||||
descriptor.set_width(size.x() as u64);
|
descriptor.set_width(size.x() as u64);
|
||||||
descriptor.set_height(size.y() as u64);
|
descriptor.set_height(size.y() as u64);
|
||||||
|
|
||||||
self.device.new_texture(&descriptor)
|
self.device.new_texture(&descriptor)
|
||||||
} else {
|
} else {
|
||||||
self.device.new_texture(&self.texture_descriptor)
|
self.device.new_texture(&self.texture_descriptor)
|
||||||
|
|
|
@ -632,6 +632,7 @@ impl Renderer {
|
||||||
) {
|
) {
|
||||||
// Snap sprite to pixel grid.
|
// Snap sprite to pixel grid.
|
||||||
let origin = (glyph.origin * scale_factor).floor() + sprite.offset.to_f32();
|
let origin = (glyph.origin * scale_factor).floor() + sprite.offset.to_f32();
|
||||||
|
|
||||||
sprites_by_atlas
|
sprites_by_atlas
|
||||||
.entry(sprite.atlas_id)
|
.entry(sprite.atlas_id)
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
|
|
|
@ -160,6 +160,15 @@ pub fn black() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn white() -> Hsla {
|
||||||
|
Hsla {
|
||||||
|
h: 0.,
|
||||||
|
s: 0.,
|
||||||
|
l: 1.,
|
||||||
|
a: 1.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Rgba> for Hsla {
|
impl From<Rgba> for Hsla {
|
||||||
fn from(color: Rgba) -> Self {
|
fn from(color: Rgba) -> Self {
|
||||||
let r = color.r;
|
let r = color.r;
|
||||||
|
|
|
@ -55,7 +55,6 @@ usvg = { version = "0.14", features = [] }
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
waker-fn = "1.1.0"
|
waker-fn = "1.1.0"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
plane-split = "0.18.0"
|
plane-split = "0.18.0"
|
||||||
bitflags = "2.4.0"
|
bitflags = "2.4.0"
|
||||||
|
|
|
@ -45,9 +45,14 @@ fn generate_shader_bindings() -> PathBuf {
|
||||||
"Pixels".into(),
|
"Pixels".into(),
|
||||||
"PointF".into(),
|
"PointF".into(),
|
||||||
"Hsla".into(),
|
"Hsla".into(),
|
||||||
"Quad".into(),
|
"ScaledContentMask".into(),
|
||||||
|
"Uniforms".into(),
|
||||||
|
"AtlasTile".into(),
|
||||||
"QuadInputIndex".into(),
|
"QuadInputIndex".into(),
|
||||||
"QuadUniforms".into(),
|
"Quad".into(),
|
||||||
|
"SpriteInputIndex".into(),
|
||||||
|
"MonochromeSprite".into(),
|
||||||
|
"PolychromeSprite".into(),
|
||||||
]);
|
]);
|
||||||
config.no_includes = true;
|
config.no_includes = true;
|
||||||
config.enumeration.prefix_with_name = true;
|
config.enumeration.prefix_with_name = true;
|
||||||
|
@ -55,11 +60,14 @@ fn generate_shader_bindings() -> PathBuf {
|
||||||
.with_src(crate_dir.join("src/scene.rs"))
|
.with_src(crate_dir.join("src/scene.rs"))
|
||||||
.with_src(crate_dir.join("src/geometry.rs"))
|
.with_src(crate_dir.join("src/geometry.rs"))
|
||||||
.with_src(crate_dir.join("src/color.rs"))
|
.with_src(crate_dir.join("src/color.rs"))
|
||||||
|
.with_src(crate_dir.join("src/window.rs"))
|
||||||
|
.with_src(crate_dir.join("src/platform.rs"))
|
||||||
.with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
|
.with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
|
||||||
.with_config(config)
|
.with_config(config)
|
||||||
.generate()
|
.generate()
|
||||||
.expect("Unable to generate bindings")
|
.expect("Unable to generate bindings")
|
||||||
.write_to_file(&output_path);
|
.write_to_file(&output_path);
|
||||||
|
|
||||||
output_path
|
output_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ pub use model_context::*;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, run_on_main, spawn_on_main, Context, LayoutId, MainThread, MainThreadOnly,
|
current_platform, image_cache::ImageCache, run_on_main, spawn_on_main, AssetSource, Context,
|
||||||
Platform, PlatformDispatcher, RootView, TextStyle, TextStyleRefinement, TextSystem, Window,
|
LayoutId, MainThread, MainThreadOnly, Platform, PlatformDispatcher, RootView, SvgRenderer,
|
||||||
WindowContext, WindowHandle, WindowId,
|
TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
|
@ -23,22 +23,33 @@ use std::{
|
||||||
mem,
|
mem,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::{
|
||||||
|
http::{self, HttpClient},
|
||||||
|
ResultExt,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct App(Arc<Mutex<MainThread<AppContext>>>);
|
pub struct App(Arc<Mutex<MainThread<AppContext>>>);
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn production() -> Self {
|
pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
|
||||||
Self::new(current_platform())
|
let http_client = http::client();
|
||||||
|
Self::new(current_platform(), asset_source, http_client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test"))]
|
#[cfg(any(test, feature = "test"))]
|
||||||
pub fn test() -> Self {
|
pub fn test() -> Self {
|
||||||
Self::new(Arc::new(super::TestPlatform::new()))
|
let platform = Arc::new(super::TestPlatform::new());
|
||||||
|
let asset_source = Arc::new(());
|
||||||
|
let http_client = util::http::FakeHttpClient::with_404_response();
|
||||||
|
Self::new(platform, asset_source, http_client)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(platform: Arc<dyn Platform>) -> Self {
|
fn new(
|
||||||
|
platform: Arc<dyn Platform>,
|
||||||
|
asset_source: Arc<dyn AssetSource>,
|
||||||
|
http_client: Arc<dyn HttpClient>,
|
||||||
|
) -> Self {
|
||||||
let dispatcher = platform.dispatcher();
|
let dispatcher = platform.dispatcher();
|
||||||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||||
let entities = EntityMap::new();
|
let entities = EntityMap::new();
|
||||||
|
@ -49,6 +60,8 @@ impl App {
|
||||||
platform: MainThreadOnly::new(platform, dispatcher.clone()),
|
platform: MainThreadOnly::new(platform, dispatcher.clone()),
|
||||||
dispatcher,
|
dispatcher,
|
||||||
text_system,
|
text_system,
|
||||||
|
svg_renderer: SvgRenderer::new(asset_source),
|
||||||
|
image_cache: ImageCache::new(http_client),
|
||||||
pending_updates: 0,
|
pending_updates: 0,
|
||||||
text_style_stack: Vec::new(),
|
text_style_stack: Vec::new(),
|
||||||
state_stacks_by_type: HashMap::default(),
|
state_stacks_by_type: HashMap::default(),
|
||||||
|
@ -83,6 +96,8 @@ pub struct AppContext {
|
||||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||||
text_system: Arc<TextSystem>,
|
text_system: Arc<TextSystem>,
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
|
pub(crate) svg_renderer: SvgRenderer,
|
||||||
|
pub(crate) image_cache: ImageCache,
|
||||||
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
|
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
|
||||||
pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
|
pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
|
||||||
pub(crate) unit_entity: Handle<()>,
|
pub(crate) unit_entity: Handle<()>,
|
||||||
|
|
|
@ -125,11 +125,12 @@ impl<T: Send + Sync> Clone for Handle<T> {
|
||||||
|
|
||||||
impl<T: Send + Sync> Drop for Handle<T> {
|
impl<T: Send + Sync> Drop for Handle<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(ref_counts) = self.ref_counts.upgrade() {
|
if let Some(_ref_counts) = self.ref_counts.upgrade() {
|
||||||
if let Some(count) = ref_counts.read().get(self.id) {
|
// todo!()
|
||||||
let prev_count = count.fetch_sub(1, SeqCst);
|
// if let Some(count) = ref_counts.read().get(self.id) {
|
||||||
assert_ne!(prev_count, 0, "Detected over-release of a handle.");
|
// let prev_count = count.fetch_sub(1, SeqCst);
|
||||||
}
|
// assert_ne!(prev_count, 0, "Detected over-release of a handle.");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
64
crates/gpui3/src/assets.rs
Normal file
64
crates/gpui3/src/assets.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::{size, DevicePixels, Result, SharedString, Size};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use image::{Bgra, ImageBuffer};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt,
|
||||||
|
hash::Hash,
|
||||||
|
sync::atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait AssetSource: 'static + Send + Sync {
|
||||||
|
fn load(&self, path: &SharedString) -> Result<Cow<[u8]>>;
|
||||||
|
fn list(&self, path: &SharedString) -> Result<Vec<SharedString>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetSource for () {
|
||||||
|
fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
|
||||||
|
Err(anyhow!(
|
||||||
|
"get called on empty asset provider with \"{}\"",
|
||||||
|
path
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&self, _path: &SharedString) -> Result<Vec<SharedString>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct ImageId(usize);
|
||||||
|
|
||||||
|
pub struct ImageData {
|
||||||
|
pub id: ImageId,
|
||||||
|
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageData {
|
||||||
|
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
|
||||||
|
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Size<DevicePixels> {
|
||||||
|
let (width, height) = self.data.dimensions();
|
||||||
|
size(width.into(), height.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ImageData {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("ImageData")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.field("size", &self.data.dimensions())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
|
||||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
|
@ -118,7 +117,7 @@ impl TryFrom<&'_ str> for Rgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
|
#[derive(Default, Copy, Clone, Debug, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Hsla {
|
pub struct Hsla {
|
||||||
pub h: f32,
|
pub h: f32,
|
||||||
|
@ -147,6 +146,15 @@ pub fn black() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn white() -> Hsla {
|
||||||
|
Hsla {
|
||||||
|
h: 0.,
|
||||||
|
s: 0.,
|
||||||
|
l: 1.,
|
||||||
|
a: 1.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hsla {
|
impl Hsla {
|
||||||
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||||
pub fn is_transparent(&self) -> bool {
|
pub fn is_transparent(&self) -> bool {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
|
AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
|
||||||
Refineable, RefinementCascade, Result, StackContext, Style, StyleHelpers, Styled, ViewContext,
|
Refineable, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -33,16 +33,9 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
|
||||||
cx: &mut ViewContext<S>,
|
cx: &mut ViewContext<S>,
|
||||||
) -> Result<(LayoutId, Self::FrameState)> {
|
) -> Result<(LayoutId, Self::FrameState)> {
|
||||||
let style = self.computed_style();
|
let style = self.computed_style();
|
||||||
let child_layout_ids = if let Some(text_style) = style.text_style(cx) {
|
let child_layout_ids = style.apply_text_style(cx, |cx| self.layout_children(view, cx))?;
|
||||||
cx.with_text_style(text_style.clone(), |cx| self.layout_children(view, cx))?
|
let layout_id = cx.request_layout(style.into(), child_layout_ids.clone())?;
|
||||||
} else {
|
Ok((layout_id, child_layout_ids))
|
||||||
self.layout_children(view, cx)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
cx.request_layout(style.into(), child_layout_ids.clone())?,
|
|
||||||
child_layout_ids,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
|
@ -56,20 +49,18 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
|
||||||
|
|
||||||
let style = self.computed_style();
|
let style = self.computed_style();
|
||||||
style.paint(order, bounds, cx);
|
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)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// // todo!("support only one dimension being hidden")
|
||||||
|
let overflow = &style.overflow;
|
||||||
|
// if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
|
||||||
|
// cx.clip(layout.bounds, style.corner_radii, || )
|
||||||
|
// }
|
||||||
|
|
||||||
|
style.apply_text_style(cx, |cx| {
|
||||||
|
style.apply_overflow(layout.bounds, cx, |cx| {
|
||||||
|
self.paint_children(overflow, state, cx)
|
||||||
|
})
|
||||||
|
})?;
|
||||||
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
|
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
|
||||||
|
|
||||||
// todo!("enable inspector")
|
// todo!("enable inspector")
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
|
use crate::{
|
||||||
|
Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled, ViewContext,
|
||||||
|
};
|
||||||
|
use futures::FutureExt;
|
||||||
use refineable::RefinementCascade;
|
use refineable::RefinementCascade;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use util::arc_cow::ArcCow;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub struct Img<S> {
|
pub struct Img<S> {
|
||||||
style: RefinementCascade<Style>,
|
style: RefinementCascade<Style>,
|
||||||
uri: Option<ArcCow<'static, str>>,
|
uri: Option<SharedString>,
|
||||||
|
grayscale: bool,
|
||||||
state_type: PhantomData<S>,
|
state_type: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,15 +17,21 @@ pub fn img<S>() -> Img<S> {
|
||||||
Img {
|
Img {
|
||||||
style: RefinementCascade::default(),
|
style: RefinementCascade::default(),
|
||||||
uri: None,
|
uri: None,
|
||||||
|
grayscale: false,
|
||||||
state_type: PhantomData,
|
state_type: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Img<S> {
|
impl<S> Img<S> {
|
||||||
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
|
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
|
||||||
self.uri = Some(uri.into());
|
self.uri = Some(uri.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
||||||
|
self.grayscale = grayscale;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> Element for Img<S> {
|
impl<S: 'static> Element for Img<S> {
|
||||||
|
@ -31,7 +41,7 @@ impl<S: 'static> Element for Img<S> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
cx: &mut crate::ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
@ -46,7 +56,7 @@ impl<S: 'static> Element for Img<S> {
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
_: &mut Self::FrameState,
|
_: &mut Self::FrameState,
|
||||||
cx: &mut crate::ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let style = self.computed_style();
|
let style = self.computed_style();
|
||||||
let order = layout.order;
|
let order = layout.order;
|
||||||
|
@ -54,36 +64,24 @@ impl<S: 'static> Element for Img<S> {
|
||||||
|
|
||||||
style.paint(order, bounds, cx);
|
style.paint(order, bounds, cx);
|
||||||
|
|
||||||
// if let Some(uri) = &self.uri {
|
if let Some(uri) = &self.uri {
|
||||||
// let image_future = cx.image_cache.get(uri.clone());
|
let image_future = cx.image_cache.get(uri.clone());
|
||||||
// if let Some(data) = image_future
|
if let Some(data) = image_future
|
||||||
// .clone()
|
.clone()
|
||||||
// .now_or_never()
|
.now_or_never()
|
||||||
// .and_then(ResultExt::log_err)
|
.and_then(ResultExt::log_err)
|
||||||
// {
|
{
|
||||||
// let rem_size = cx.rem_size();
|
cx.paint_image(bounds, order, data, self.grayscale)?;
|
||||||
// cx.scene().push_image(scene::Image {
|
} else {
|
||||||
// bounds,
|
log::warn!("image not loaded yet");
|
||||||
// border: gpui::Border {
|
// cx.spawn(|this, mut cx| async move {
|
||||||
// color: style.border_color.unwrap_or_default().into(),
|
// if image_future.await.log_err().is_some() {
|
||||||
// top: style.border_widths.top.to_pixels(rem_size),
|
// this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||||
// 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),
|
// .detach();
|
||||||
// },
|
}
|
||||||
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
|
use crate::{Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled};
|
||||||
use refineable::RefinementCascade;
|
use refineable::RefinementCascade;
|
||||||
use std::{borrow::Cow, marker::PhantomData};
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct Svg<S> {
|
pub struct Svg<S> {
|
||||||
path: Option<Cow<'static, str>>,
|
path: Option<SharedString>,
|
||||||
style: RefinementCascade<Style>,
|
style: RefinementCascade<Style>,
|
||||||
state_type: PhantomData<S>,
|
state_type: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ pub fn svg<S>() -> Svg<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Svg<S> {
|
impl<S> Svg<S> {
|
||||||
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
|
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||||
self.path = Some(path.into());
|
self.path = Some(path.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -41,28 +41,18 @@ impl<S: 'static> Element for Svg<S> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
_layout: Layout,
|
layout: Layout,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
_: &mut Self::FrameState,
|
_: &mut Self::FrameState,
|
||||||
_cx: &mut crate::ViewContext<S>,
|
cx: &mut crate::ViewContext<S>,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
// todo!
|
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||||
// 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((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
cx.paint_svg(layout.bounds, layout.order, path.clone(), 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,15 +32,13 @@ pub struct Text<S> {
|
||||||
|
|
||||||
impl<S: 'static> Element for Text<S> {
|
impl<S: 'static> Element for Text<S> {
|
||||||
type State = S;
|
type State = S;
|
||||||
type FrameState = Arc<Mutex<Option<TextLayout>>>;
|
type FrameState = Arc<Mutex<Option<TextFrameState>>>;
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_view: &mut S,
|
_view: &mut S,
|
||||||
cx: &mut ViewContext<S>,
|
cx: &mut ViewContext<S>,
|
||||||
) -> Result<(LayoutId, Self::FrameState)> {
|
) -> Result<(LayoutId, Self::FrameState)> {
|
||||||
dbg!("layout text");
|
|
||||||
|
|
||||||
let text_system = cx.text_system().clone();
|
let text_system = cx.text_system().clone();
|
||||||
let text_style = cx.text_style();
|
let text_style = cx.text_style();
|
||||||
let font_size = text_style.font_size * cx.rem_size();
|
let font_size = text_style.font_size * cx.rem_size();
|
||||||
|
@ -48,13 +46,12 @@ impl<S: 'static> Element for Text<S> {
|
||||||
.line_height
|
.line_height
|
||||||
.to_pixels(font_size.into(), cx.rem_size());
|
.to_pixels(font_size.into(), cx.rem_size());
|
||||||
let text = self.text.clone();
|
let text = self.text.clone();
|
||||||
let paint_state = Arc::new(Mutex::new(None));
|
let frame_state = Arc::new(Mutex::new(None));
|
||||||
|
|
||||||
let rem_size = cx.rem_size();
|
let rem_size = cx.rem_size();
|
||||||
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
|
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
|
||||||
let frame_state = paint_state.clone();
|
let frame_state = frame_state.clone();
|
||||||
move |_, _| {
|
move |_, _| {
|
||||||
dbg!("starting measurement");
|
|
||||||
let Some(line_layout) = text_system
|
let Some(line_layout) = text_system
|
||||||
.layout_line(
|
.layout_line(
|
||||||
text.as_ref(),
|
text.as_ref(),
|
||||||
|
@ -65,57 +62,51 @@ impl<S: 'static> Element for Text<S> {
|
||||||
else {
|
else {
|
||||||
return Size::default();
|
return Size::default();
|
||||||
};
|
};
|
||||||
dbg!("bbbb");
|
|
||||||
|
|
||||||
let size = Size {
|
let size = Size {
|
||||||
width: line_layout.width(),
|
width: line_layout.width(),
|
||||||
height: line_height,
|
height: line_height,
|
||||||
};
|
};
|
||||||
|
|
||||||
frame_state.lock().replace(TextLayout {
|
frame_state.lock().replace(TextFrameState {
|
||||||
line: Arc::new(line_layout),
|
line: Arc::new(line_layout),
|
||||||
line_height,
|
line_height,
|
||||||
});
|
});
|
||||||
|
|
||||||
dbg!(size)
|
size
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dbg!("got to end of text layout");
|
Ok((layout_id?, frame_state))
|
||||||
Ok((layout_id?, paint_state))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint<'a>(
|
fn paint<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
paint_state: &mut Self::FrameState,
|
frame_state: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<S>,
|
cx: &mut ViewContext<S>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let bounds = layout.bounds;
|
|
||||||
|
|
||||||
let line;
|
let line;
|
||||||
let line_height;
|
let line_height;
|
||||||
{
|
{
|
||||||
let paint_state = paint_state.lock();
|
let frame_state = frame_state.lock();
|
||||||
let paint_state = paint_state
|
let frame_state = frame_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("measurement has not been performed");
|
.expect("measurement has not been performed");
|
||||||
line = paint_state.line.clone();
|
line = frame_state.line.clone();
|
||||||
line_height = paint_state.line_height;
|
line_height = frame_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.");
|
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
|
||||||
let visible_bounds = bounds;
|
let visible_bounds = layout.bounds;
|
||||||
line.paint(bounds.origin, visible_bounds, line_height, cx)?;
|
line.paint(&layout, visible_bounds, line_height, cx)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextLayout {
|
pub struct TextFrameState {
|
||||||
line: Arc<Line>,
|
line: Arc<Line>,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use bytemuck::{Pod, Zeroable};
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
|
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
@ -29,12 +28,21 @@ impl<T: Clone + Debug> Point<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Point<Pixels> {
|
||||||
|
pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
|
||||||
|
Point {
|
||||||
|
x: self.x.scale(factor),
|
||||||
|
y: self.y.scale(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, Rhs> Mul<Rhs> for Point<T>
|
impl<T, Rhs> Mul<Rhs> for Point<T>
|
||||||
where
|
where
|
||||||
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
|
T: Mul<Rhs, Output = T> + Clone + Debug,
|
||||||
Rhs: Clone + Debug,
|
Rhs: Clone + Debug,
|
||||||
{
|
{
|
||||||
type Output = Point<Rhs>;
|
type Output = Point<T>;
|
||||||
|
|
||||||
fn mul(self, rhs: Rhs) -> Self::Output {
|
fn mul(self, rhs: Rhs) -> Self::Output {
|
||||||
Point {
|
Point {
|
||||||
|
@ -102,10 +110,7 @@ impl<T: Clone + Debug> Clone for Point<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
|
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)]
|
||||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
|
|
||||||
|
|
||||||
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div)]
|
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Size<T: Clone + Debug> {
|
pub struct Size<T: Clone + Debug> {
|
||||||
|
@ -113,9 +118,6 @@ pub struct Size<T: Clone + Debug> {
|
||||||
pub height: 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> {
|
pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
|
||||||
Size { width, height }
|
Size { width, height }
|
||||||
}
|
}
|
||||||
|
@ -129,6 +131,32 @@ impl<T: Clone + Debug> Size<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Size<Pixels> {
|
||||||
|
pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
|
||||||
|
Size {
|
||||||
|
width: self.width.scale(factor),
|
||||||
|
height: self.height.scale(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Debug + Ord> Size<T> {
|
||||||
|
pub fn max(&self, other: &Self) -> Self {
|
||||||
|
Size {
|
||||||
|
width: if self.width >= other.width {
|
||||||
|
self.width.clone()
|
||||||
|
} else {
|
||||||
|
other.width.clone()
|
||||||
|
},
|
||||||
|
height: if self.height >= other.height {
|
||||||
|
self.height.clone()
|
||||||
|
} else {
|
||||||
|
other.height.clone()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, Rhs> Mul<Rhs> for Size<T>
|
impl<T, Rhs> Mul<Rhs> for Size<T>
|
||||||
where
|
where
|
||||||
T: Mul<Rhs, Output = Rhs> + Debug + Clone,
|
T: Mul<Rhs, Output = Rhs> + Debug + Clone,
|
||||||
|
@ -151,11 +179,13 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Eq + Debug + Clone> Eq for Size<T> {}
|
||||||
|
|
||||||
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
|
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
|
||||||
fn from(val: Size<Option<Pixels>>) -> Self {
|
fn from(size: Size<Option<Pixels>>) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: val.width.map(|p| p.0 as f32),
|
width: size.width.map(|p| p.0 as f32),
|
||||||
height: val.height.map(|p| p.0 as f32),
|
height: size.height.map(|p| p.0 as f32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,7 +217,7 @@ impl Size<Length> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Refineable, Clone, Default, Debug, PartialEq)]
|
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Bounds<T: Clone + Debug> {
|
pub struct Bounds<T: Clone + Debug> {
|
||||||
|
@ -195,13 +225,24 @@ pub struct Bounds<T: Clone + Debug> {
|
||||||
pub size: Size<T>,
|
pub size: Size<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Bounds<T> {}
|
impl<T: Clone + Debug + Sub<Output = T>> Bounds<T> {
|
||||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
|
pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
|
||||||
|
let origin = Point {
|
||||||
|
x: upper_left.x.clone(),
|
||||||
|
y: upper_left.y.clone(),
|
||||||
|
};
|
||||||
|
let size = Size {
|
||||||
|
width: lower_right.x - upper_left.x,
|
||||||
|
height: lower_right.y - upper_left.y,
|
||||||
|
};
|
||||||
|
Bounds { origin, size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bounds<f32> * Pixels = Bounds<Pixels>
|
|
||||||
impl<T, Rhs> Mul<Rhs> for Bounds<T>
|
impl<T, Rhs> Mul<Rhs> for Bounds<T>
|
||||||
where
|
where
|
||||||
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
|
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
|
||||||
|
Point<T>: Mul<Rhs, Output = Point<Rhs>>,
|
||||||
Rhs: Clone + Debug,
|
Rhs: Clone + Debug,
|
||||||
{
|
{
|
||||||
type Output = Bounds<Rhs>;
|
type Output = Bounds<Rhs>;
|
||||||
|
@ -267,9 +308,18 @@ impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Bounds<Pixels> {
|
||||||
|
pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
|
||||||
|
Bounds {
|
||||||
|
origin: self.origin.scale(factor),
|
||||||
|
size: self.size.scale(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
|
impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
|
||||||
|
|
||||||
#[derive(Refineable, Clone, Default, Debug)]
|
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Edges<T: Clone + Debug> {
|
pub struct Edges<T: Clone + Debug> {
|
||||||
|
@ -303,10 +353,6 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Edges<T>
|
||||||
|
|
||||||
impl<T: Clone + Debug + Copy> Copy for Edges<T> {}
|
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> {
|
impl<T: Clone + Debug> Edges<T> {
|
||||||
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Edges<U> {
|
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Edges<U> {
|
||||||
Edges {
|
Edges {
|
||||||
|
@ -376,7 +422,7 @@ impl Edges<AbsoluteLength> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Refineable, Clone, Default, Debug)]
|
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Corners<T: Clone + Debug> {
|
pub struct Corners<T: Clone + Debug> {
|
||||||
|
@ -386,6 +432,28 @@ pub struct Corners<T: Clone + Debug> {
|
||||||
pub bottom_left: T,
|
pub bottom_left: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Corners<AbsoluteLength> {
|
||||||
|
pub fn to_pixels(&self, rem_size: Pixels) -> Corners<Pixels> {
|
||||||
|
Corners {
|
||||||
|
top_left: self.top_left.to_pixels(rem_size),
|
||||||
|
top_right: self.top_right.to_pixels(rem_size),
|
||||||
|
bottom_right: self.bottom_right.to_pixels(rem_size),
|
||||||
|
bottom_left: self.bottom_left.to_pixels(rem_size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Corners<Pixels> {
|
||||||
|
pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
|
||||||
|
Corners {
|
||||||
|
top_left: self.top_left.scale(factor),
|
||||||
|
top_right: self.top_right.scale(factor),
|
||||||
|
bottom_right: self.bottom_right.scale(factor),
|
||||||
|
bottom_left: self.bottom_left.scale(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone + Debug> Corners<T> {
|
impl<T: Clone + Debug> Corners<T> {
|
||||||
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Corners<U> {
|
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Corners<U> {
|
||||||
Corners {
|
Corners {
|
||||||
|
@ -421,10 +489,6 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Corners<T
|
||||||
|
|
||||||
impl<T: Clone + Debug + Copy> Copy for Corners<T> {}
|
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)]
|
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Pixels(pub(crate) f32);
|
pub struct Pixels(pub(crate) f32);
|
||||||
|
@ -445,13 +509,19 @@ impl Mul<Pixels> for f32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MulAssign<f32> for Pixels {
|
||||||
|
fn mul_assign(&mut self, other: f32) {
|
||||||
|
self.0 *= other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Pixels {
|
impl Pixels {
|
||||||
pub fn round(&self) -> Self {
|
pub fn round(&self) -> Self {
|
||||||
Self(self.0.round())
|
Self(self.0.round())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_device_pixels(&self, scale: f32) -> DevicePixels {
|
pub fn scale(&self, factor: f32) -> ScaledPixels {
|
||||||
DevicePixels((self.0 * scale).ceil() as u32)
|
ScaledPixels(self.0 * factor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,20 +548,17 @@ impl std::hash::Hash for Pixels {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for Pixels {
|
impl From<f64> for Pixels {
|
||||||
fn from(val: f64) -> Self {
|
fn from(pixels: f64) -> Self {
|
||||||
Pixels(val as f32)
|
Pixels(pixels as f32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f32> for Pixels {
|
impl From<f32> for Pixels {
|
||||||
fn from(val: f32) -> Self {
|
fn from(pixels: f32) -> Self {
|
||||||
Pixels(val)
|
Pixels(pixels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl bytemuck::Pod for Pixels {}
|
|
||||||
unsafe impl bytemuck::Zeroable for Pixels {}
|
|
||||||
|
|
||||||
impl Debug for Pixels {
|
impl Debug for Pixels {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{} px", self.0)
|
write!(f, "{} px", self.0)
|
||||||
|
@ -517,23 +584,90 @@ impl From<Pixels> for f64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
|
Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
|
||||||
)]
|
)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct DevicePixels(pub(crate) u32);
|
pub struct DevicePixels(pub(crate) i32);
|
||||||
|
|
||||||
unsafe impl bytemuck::Pod for DevicePixels {}
|
impl DevicePixels {
|
||||||
unsafe impl bytemuck::Zeroable for DevicePixels {}
|
pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
|
||||||
|
self.0 as u32 * bytes_per_pixel as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<DevicePixels> for u32 {
|
impl std::fmt::Debug for DevicePixels {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} px (device)", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DevicePixels> for i32 {
|
||||||
fn from(device_pixels: DevicePixels) -> Self {
|
fn from(device_pixels: DevicePixels) -> Self {
|
||||||
device_pixels.0
|
device_pixels.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<i32> for DevicePixels {
|
||||||
|
fn from(device_pixels: i32) -> Self {
|
||||||
|
DevicePixels(device_pixels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u32> for DevicePixels {
|
impl From<u32> for DevicePixels {
|
||||||
fn from(val: u32) -> Self {
|
fn from(device_pixels: u32) -> Self {
|
||||||
DevicePixels(val)
|
DevicePixels(device_pixels as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DevicePixels> for u32 {
|
||||||
|
fn from(device_pixels: DevicePixels) -> Self {
|
||||||
|
device_pixels.0 as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DevicePixels> for u64 {
|
||||||
|
fn from(device_pixels: DevicePixels) -> Self {
|
||||||
|
device_pixels.0 as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for DevicePixels {
|
||||||
|
fn from(device_pixels: u64) -> Self {
|
||||||
|
DevicePixels(device_pixels as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct ScaledPixels(pub(crate) f32);
|
||||||
|
|
||||||
|
impl ScaledPixels {
|
||||||
|
pub fn floor(&self) -> Self {
|
||||||
|
Self(self.0.floor())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ceil(&self) -> Self {
|
||||||
|
Self(self.0.ceil())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ScaledPixels {}
|
||||||
|
|
||||||
|
impl Debug for ScaledPixels {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} px (scaled)", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ScaledPixels> for DevicePixels {
|
||||||
|
fn from(scaled: ScaledPixels) -> Self {
|
||||||
|
DevicePixels(scaled.0.ceil() as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DevicePixels> for ScaledPixels {
|
||||||
|
fn from(device: DevicePixels) -> Self {
|
||||||
|
ScaledPixels(device.0 as f32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,3 +855,76 @@ impl From<()> for Length {
|
||||||
Self::Definite(DefiniteLength::default())
|
Self::Definite(DefiniteLength::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IsZero {
|
||||||
|
fn is_zero(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for DevicePixels {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for ScaledPixels {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for Pixels {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for Rems {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for AbsoluteLength {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AbsoluteLength::Pixels(pixels) => pixels.is_zero(),
|
||||||
|
AbsoluteLength::Rems(rems) => rems.is_zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for DefiniteLength {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DefiniteLength::Absolute(length) => length.is_zero(),
|
||||||
|
DefiniteLength::Fraction(fraction) => *fraction == 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsZero for Length {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Length::Definite(length) => length.is_zero(),
|
||||||
|
Length::Auto => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IsZero + Debug + Clone> IsZero for Point<T> {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.x.is_zero() && self.y.is_zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IsZero + Debug + Clone> IsZero for Size<T> {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.width.is_zero() || self.height.is_zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IsZero + Debug + Clone> IsZero for Bounds<T> {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.origin.is_zero() && self.size.is_zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
mod app;
|
mod app;
|
||||||
|
mod assets;
|
||||||
mod color;
|
mod color;
|
||||||
mod element;
|
mod element;
|
||||||
mod elements;
|
mod elements;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
|
mod image_cache;
|
||||||
mod platform;
|
mod platform;
|
||||||
mod scene;
|
mod scene;
|
||||||
mod style;
|
mod style;
|
||||||
mod style_helpers;
|
mod style_helpers;
|
||||||
mod styled;
|
mod styled;
|
||||||
|
mod svg_renderer;
|
||||||
mod taffy;
|
mod taffy;
|
||||||
mod text_system;
|
mod text_system;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -17,12 +20,15 @@ mod window;
|
||||||
|
|
||||||
pub use anyhow::Result;
|
pub use anyhow::Result;
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
|
pub use assets::*;
|
||||||
pub use color::*;
|
pub use color::*;
|
||||||
pub use element::*;
|
pub use element::*;
|
||||||
pub use elements::*;
|
pub use elements::*;
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
pub use geometry::*;
|
pub use geometry::*;
|
||||||
pub use gpui3_macros::*;
|
pub use gpui3_macros::*;
|
||||||
|
pub use svg_renderer::*;
|
||||||
|
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use refineable::*;
|
pub use refineable::*;
|
||||||
pub use scene::*;
|
pub use scene::*;
|
||||||
|
@ -83,16 +89,16 @@ impl<T> DerefMut for MainThread<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait StackContext {
|
pub trait BorrowAppContext {
|
||||||
fn app(&mut self) -> &mut AppContext;
|
fn app_mut(&mut self) -> &mut AppContext;
|
||||||
|
|
||||||
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&mut Self) -> R,
|
||||||
{
|
{
|
||||||
self.app().push_text_style(style);
|
self.app_mut().push_text_style(style);
|
||||||
let result = f(self);
|
let result = f(self);
|
||||||
self.app().pop_text_style();
|
self.app_mut().pop_text_style();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,9 +106,9 @@ pub trait StackContext {
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&mut Self) -> R,
|
||||||
{
|
{
|
||||||
self.app().push_state(state);
|
self.app_mut().push_state(state);
|
||||||
let result = f(self);
|
let result = f(self);
|
||||||
self.app().pop_state::<T>();
|
self.app_mut().pop_state::<T>();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,6 +150,12 @@ impl std::fmt::Debug for SharedString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SharedString {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self(value.into())
|
Self(value.into())
|
||||||
|
|
99
crates/gpui3/src/image_cache.rs
Normal file
99
crates/gpui3/src/image_cache.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use crate::{ImageData, ImageId, SharedString};
|
||||||
|
use collections::HashMap;
|
||||||
|
use futures::{
|
||||||
|
future::{BoxFuture, Shared},
|
||||||
|
AsyncReadExt, FutureExt,
|
||||||
|
};
|
||||||
|
use image::ImageError;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
use util::http::{self, HttpClient};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||||
|
pub struct RenderImageParams {
|
||||||
|
pub(crate) image_id: ImageId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Clone)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("http error: {0}")]
|
||||||
|
Client(#[from] http::Error),
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(Arc<std::io::Error>),
|
||||||
|
#[error("unexpected http status: {status}, body: {body}")]
|
||||||
|
BadStatus {
|
||||||
|
status: http::StatusCode,
|
||||||
|
body: String,
|
||||||
|
},
|
||||||
|
#[error("image error: {0}")]
|
||||||
|
Image(Arc<ImageError>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(error: std::io::Error) -> Self {
|
||||||
|
Error::Io(Arc::new(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ImageError> for Error {
|
||||||
|
fn from(error: ImageError) -> Self {
|
||||||
|
Error::Image(Arc::new(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ImageCache {
|
||||||
|
client: Arc<dyn HttpClient>,
|
||||||
|
images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
|
||||||
|
|
||||||
|
impl ImageCache {
|
||||||
|
pub fn new(client: Arc<dyn HttpClient>) -> Self {
|
||||||
|
ImageCache {
|
||||||
|
client,
|
||||||
|
images: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(
|
||||||
|
&self,
|
||||||
|
uri: impl Into<SharedString>,
|
||||||
|
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
|
||||||
|
let uri = uri.into();
|
||||||
|
let mut images = self.images.lock();
|
||||||
|
|
||||||
|
match images.get(&uri) {
|
||||||
|
Some(future) => future.clone(),
|
||||||
|
None => {
|
||||||
|
let client = self.client.clone();
|
||||||
|
let future = {
|
||||||
|
let uri = uri.clone();
|
||||||
|
async move {
|
||||||
|
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
|
||||||
|
let mut body = Vec::new();
|
||||||
|
response.body_mut().read_to_end(&mut body).await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(Error::BadStatus {
|
||||||
|
status: response.status(),
|
||||||
|
body: String::from_utf8_lossy(&body).into_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = image::guess_format(&body)?;
|
||||||
|
let image =
|
||||||
|
image::load_from_memory_with_format(&body, format)?.into_bgra8();
|
||||||
|
Ok(Arc::new(ImageData::new(image)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
.shared();
|
||||||
|
|
||||||
|
images.insert(uri, future.clone());
|
||||||
|
future
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,15 +5,17 @@ mod mac;
|
||||||
#[cfg(any(test, feature = "test"))]
|
#[cfg(any(test, feature = "test"))]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
|
use crate::image_cache::RenderImageParams;
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
|
AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
|
||||||
Scene, SharedString, Size,
|
RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use seahash::SeaHasher;
|
use seahash::SeaHasher;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -122,7 +124,7 @@ pub trait PlatformWindow {
|
||||||
fn screen(&self) -> Rc<dyn PlatformScreen>;
|
fn screen(&self) -> Rc<dyn PlatformScreen>;
|
||||||
fn mouse_position(&self) -> Point<Pixels>;
|
fn mouse_position(&self) -> Point<Pixels>;
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
|
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
|
||||||
fn prompt(
|
fn prompt(
|
||||||
&self,
|
&self,
|
||||||
level: WindowPromptLevel,
|
level: WindowPromptLevel,
|
||||||
|
@ -146,6 +148,8 @@ pub trait PlatformWindow {
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||||
fn draw(&self, scene: Scene);
|
fn draw(&self, scene: Scene);
|
||||||
|
|
||||||
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PlatformDispatcher: Send + Sync {
|
pub trait PlatformDispatcher: Send + Sync {
|
||||||
|
@ -161,16 +165,9 @@ pub trait PlatformTextSystem: Send + Sync {
|
||||||
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
|
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 advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
|
||||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
|
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
|
||||||
fn rasterize_glyph(
|
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
|
||||||
&self,
|
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
||||||
font_id: FontId,
|
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
|
||||||
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(
|
fn wrap_line(
|
||||||
&self,
|
&self,
|
||||||
text: &str,
|
text: &str,
|
||||||
|
@ -180,7 +177,80 @@ pub trait PlatformTextSystem: Send + Sync {
|
||||||
) -> Vec<usize>;
|
) -> Vec<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InputHandler {
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||||
|
pub enum AtlasKey {
|
||||||
|
Glyph(RenderGlyphParams),
|
||||||
|
Svg(RenderSvgParams),
|
||||||
|
Image(RenderImageParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtlasKey {
|
||||||
|
pub fn is_monochrome(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AtlasKey::Glyph(params) => !params.is_emoji,
|
||||||
|
AtlasKey::Svg(_) => true,
|
||||||
|
AtlasKey::Image(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RenderGlyphParams> for AtlasKey {
|
||||||
|
fn from(params: RenderGlyphParams) -> Self {
|
||||||
|
Self::Glyph(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RenderSvgParams> for AtlasKey {
|
||||||
|
fn from(params: RenderSvgParams) -> Self {
|
||||||
|
Self::Svg(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RenderImageParams> for AtlasKey {
|
||||||
|
fn from(params: RenderImageParams) -> Self {
|
||||||
|
Self::Image(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PlatformAtlas: Send + Sync {
|
||||||
|
fn get_or_insert_with<'a>(
|
||||||
|
&self,
|
||||||
|
key: &AtlasKey,
|
||||||
|
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
|
||||||
|
) -> Result<AtlasTile>;
|
||||||
|
|
||||||
|
fn clear(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AtlasTile {
|
||||||
|
pub(crate) texture_id: AtlasTextureId,
|
||||||
|
pub(crate) tile_id: TileId,
|
||||||
|
pub(crate) bounds: Bounds<DevicePixels>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct AtlasTextureId(pub(crate) u32); // We use u32 instead of usize for Metal Shader Language compatibility
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct TileId(pub(crate) u32);
|
||||||
|
|
||||||
|
impl From<etagere::AllocId> for TileId {
|
||||||
|
fn from(id: etagere::AllocId) -> Self {
|
||||||
|
Self(id.serialize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TileId> for etagere::AllocId {
|
||||||
|
fn from(id: TileId) -> Self {
|
||||||
|
Self::deserialize(id.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PlatformInputHandler {
|
||||||
fn selected_text_range(&self) -> Option<Range<usize>>;
|
fn selected_text_range(&self) -> Option<Range<usize>>;
|
||||||
fn marked_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 text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
|
||||||
|
@ -198,12 +268,6 @@ pub trait InputHandler {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct ScreenId(pub(crate) Uuid);
|
pub struct ScreenId(pub(crate) Uuid);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum RasterizationOptions {
|
|
||||||
Alpha,
|
|
||||||
Bgra,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
pub bounds: WindowBounds,
|
pub bounds: WindowBounds,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///! an origin at the bottom left of the main display.
|
///! an origin at the bottom left of the main display.
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod events;
|
mod events;
|
||||||
|
mod metal_atlas;
|
||||||
mod metal_renderer;
|
mod metal_renderer;
|
||||||
mod open_type;
|
mod open_type;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
@ -30,6 +31,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use dispatcher::*;
|
pub use dispatcher::*;
|
||||||
|
pub use metal_atlas::*;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use screen::*;
|
pub use screen::*;
|
||||||
pub use text_system::*;
|
pub use text_system::*;
|
||||||
|
|
185
crates/gpui3/src/platform/mac/metal_atlas.rs
Normal file
185
crates/gpui3/src/platform/mac/metal_atlas.rs
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size,
|
||||||
|
};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use collections::HashMap;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use etagere::BucketedAtlasAllocator;
|
||||||
|
use metal::Device;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||||
|
|
||||||
|
impl MetalAtlas {
|
||||||
|
pub fn new(device: Device) -> Self {
|
||||||
|
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||||
|
device: AssertSend(device),
|
||||||
|
textures: Default::default(),
|
||||||
|
tiles_by_key: Default::default(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn texture(&self, id: AtlasTextureId) -> metal::Texture {
|
||||||
|
self.0.lock().textures[id.0 as usize].metal_texture.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MetalAtlasState {
|
||||||
|
device: AssertSend<Device>,
|
||||||
|
textures: Vec<MetalAtlasTexture>,
|
||||||
|
tiles_by_key: HashMap<AtlasKey, AtlasTile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformAtlas for MetalAtlas {
|
||||||
|
fn get_or_insert_with<'a>(
|
||||||
|
&self,
|
||||||
|
key: &AtlasKey,
|
||||||
|
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
|
||||||
|
) -> Result<AtlasTile> {
|
||||||
|
let mut lock = self.0.lock();
|
||||||
|
if let Some(tile) = lock.tiles_by_key.get(key) {
|
||||||
|
return Ok(tile.clone());
|
||||||
|
} else {
|
||||||
|
let (size, bytes) = build()?;
|
||||||
|
let tile = lock
|
||||||
|
.textures
|
||||||
|
.iter_mut()
|
||||||
|
.rev()
|
||||||
|
.find_map(|texture| {
|
||||||
|
if texture.monochrome == key.is_monochrome() {
|
||||||
|
texture.upload(size, &bytes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
let texture = lock.push_texture(size, key.is_monochrome());
|
||||||
|
texture.upload(size, &bytes)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow!("could not allocate in new texture"))?;
|
||||||
|
lock.tiles_by_key.insert(key.clone(), tile.clone());
|
||||||
|
Ok(tile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&self) {
|
||||||
|
self.0.lock().tiles_by_key.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetalAtlasState {
|
||||||
|
fn push_texture(
|
||||||
|
&mut self,
|
||||||
|
min_size: Size<DevicePixels>,
|
||||||
|
monochrome: bool,
|
||||||
|
) -> &mut MetalAtlasTexture {
|
||||||
|
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||||
|
width: DevicePixels(1024),
|
||||||
|
height: DevicePixels(1024),
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = min_size.max(&DEFAULT_ATLAS_SIZE);
|
||||||
|
let texture_descriptor = metal::TextureDescriptor::new();
|
||||||
|
texture_descriptor.set_width(size.width.into());
|
||||||
|
texture_descriptor.set_height(size.height.into());
|
||||||
|
if monochrome {
|
||||||
|
texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm);
|
||||||
|
} else {
|
||||||
|
texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
|
||||||
|
}
|
||||||
|
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||||
|
|
||||||
|
let atlas_texture = MetalAtlasTexture {
|
||||||
|
id: AtlasTextureId(self.textures.len() as u32),
|
||||||
|
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||||
|
metal_texture: AssertSend(metal_texture),
|
||||||
|
monochrome,
|
||||||
|
};
|
||||||
|
self.textures.push(atlas_texture);
|
||||||
|
self.textures.last_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MetalAtlasTexture {
|
||||||
|
id: AtlasTextureId,
|
||||||
|
allocator: BucketedAtlasAllocator,
|
||||||
|
metal_texture: AssertSend<metal::Texture>,
|
||||||
|
monochrome: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetalAtlasTexture {
|
||||||
|
fn upload(&mut self, size: Size<DevicePixels>, bytes: &[u8]) -> Option<AtlasTile> {
|
||||||
|
let allocation = self.allocator.allocate(size.into())?;
|
||||||
|
let tile = AtlasTile {
|
||||||
|
texture_id: self.id,
|
||||||
|
tile_id: allocation.id.into(),
|
||||||
|
bounds: Bounds {
|
||||||
|
origin: allocation.rectangle.min.into(),
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let region = metal::MTLRegion::new_2d(
|
||||||
|
tile.bounds.origin.x.into(),
|
||||||
|
tile.bounds.origin.y.into(),
|
||||||
|
tile.bounds.size.width.into(),
|
||||||
|
tile.bounds.size.height.into(),
|
||||||
|
);
|
||||||
|
self.metal_texture.replace_region(
|
||||||
|
region,
|
||||||
|
0,
|
||||||
|
bytes.as_ptr() as *const _,
|
||||||
|
u32::from(tile.bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
|
||||||
|
);
|
||||||
|
Some(tile)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bytes_per_pixel(&self) -> u8 {
|
||||||
|
use metal::MTLPixelFormat::*;
|
||||||
|
match self.metal_texture.pixel_format() {
|
||||||
|
A8Unorm | R8Unorm => 1,
|
||||||
|
RGBA8Unorm | BGRA8Unorm => 4,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Size<DevicePixels>> for etagere::Size {
|
||||||
|
fn from(size: Size<DevicePixels>) -> Self {
|
||||||
|
etagere::Size::new(size.width.into(), size.height.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<etagere::Point> for Point<DevicePixels> {
|
||||||
|
fn from(value: etagere::Point) -> Self {
|
||||||
|
Point {
|
||||||
|
x: DevicePixels::from(value.x),
|
||||||
|
y: DevicePixels::from(value.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<etagere::Size> for Size<DevicePixels> {
|
||||||
|
fn from(size: etagere::Size) -> Self {
|
||||||
|
Size {
|
||||||
|
width: DevicePixels::from(size.width),
|
||||||
|
height: DevicePixels::from(size.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<etagere::Rectangle> for Bounds<DevicePixels> {
|
||||||
|
fn from(rectangle: etagere::Rectangle) -> Self {
|
||||||
|
Bounds {
|
||||||
|
origin: rectangle.min.into(),
|
||||||
|
size: rectangle.size().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
|
struct AssertSend<T>(T);
|
||||||
|
|
||||||
|
unsafe impl<T> Send for AssertSend<T> {}
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::{point, size, DevicePixels, Quad, Scene, Size};
|
use crate::{
|
||||||
use bytemuck::{Pod, Zeroable};
|
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
|
||||||
|
Quad, Scene, Size,
|
||||||
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
base::{NO, YES},
|
base::{NO, YES},
|
||||||
foundation::NSUInteger,
|
foundation::NSUInteger,
|
||||||
|
@ -7,18 +9,20 @@ use cocoa::{
|
||||||
};
|
};
|
||||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||||
use objc::{self, msg_send, sel, sel_impl};
|
use objc::{self, msg_send, sel, sel_impl};
|
||||||
use std::{ffi::c_void, mem, ptr};
|
use std::{ffi::c_void, mem, ptr, sync::Arc};
|
||||||
|
|
||||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
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.
|
const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
|
||||||
|
|
||||||
pub struct MetalRenderer {
|
pub struct MetalRenderer {
|
||||||
device: metal::Device,
|
|
||||||
layer: metal::MetalLayer,
|
layer: metal::MetalLayer,
|
||||||
command_queue: CommandQueue,
|
command_queue: CommandQueue,
|
||||||
quad_pipeline_state: metal::RenderPipelineState,
|
quads_pipeline_state: metal::RenderPipelineState,
|
||||||
|
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
|
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
unit_vertices: metal::Buffer,
|
unit_vertices: metal::Buffer,
|
||||||
instances: metal::Buffer,
|
instances: metal::Buffer,
|
||||||
|
sprite_atlas: Arc<MetalAtlas>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetalRenderer {
|
impl MetalRenderer {
|
||||||
|
@ -78,23 +82,43 @@ impl MetalRenderer {
|
||||||
MTLResourceOptions::StorageModeManaged,
|
MTLResourceOptions::StorageModeManaged,
|
||||||
);
|
);
|
||||||
|
|
||||||
let quad_pipeline_state = build_pipeline_state(
|
let quads_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
"quad",
|
"quads",
|
||||||
"quad_vertex",
|
"quad_vertex",
|
||||||
"quad_fragment",
|
"quad_fragment",
|
||||||
PIXEL_FORMAT,
|
PIXEL_FORMAT,
|
||||||
);
|
);
|
||||||
|
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"monochrome_sprites",
|
||||||
|
"monochrome_sprite_vertex",
|
||||||
|
"monochrome_sprite_fragment",
|
||||||
|
PIXEL_FORMAT,
|
||||||
|
);
|
||||||
|
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"polychrome_sprites",
|
||||||
|
"polychrome_sprite_vertex",
|
||||||
|
"polychrome_sprite_fragment",
|
||||||
|
PIXEL_FORMAT,
|
||||||
|
);
|
||||||
|
|
||||||
let command_queue = device.new_command_queue();
|
let command_queue = device.new_command_queue();
|
||||||
|
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
device,
|
|
||||||
layer,
|
layer,
|
||||||
command_queue,
|
command_queue,
|
||||||
quad_pipeline_state,
|
quads_pipeline_state,
|
||||||
|
monochrome_sprites_pipeline_state,
|
||||||
|
polychrome_sprites_pipeline_state,
|
||||||
unit_vertices,
|
unit_vertices,
|
||||||
instances,
|
instances,
|
||||||
|
sprite_atlas,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +126,16 @@ impl MetalRenderer {
|
||||||
&*self.layer
|
&*self.layer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &Scene) {
|
pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
|
||||||
dbg!(scene);
|
&self.sprite_atlas
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self, scene: &mut Scene) {
|
||||||
let layer = self.layer.clone();
|
let layer = self.layer.clone();
|
||||||
let viewport_size = layer.drawable_size();
|
let viewport_size = layer.drawable_size();
|
||||||
let viewport_size: Size<DevicePixels> = size(
|
let viewport_size: Size<DevicePixels> = size(
|
||||||
(viewport_size.width.ceil() as u32).into(),
|
(viewport_size.width.ceil() as i32).into(),
|
||||||
(viewport_size.height.ceil() as u32).into(),
|
(viewport_size.height.ceil() as i32).into(),
|
||||||
);
|
);
|
||||||
let drawable = if let Some(drawable) = layer.next_drawable() {
|
let drawable = if let Some(drawable) = layer.next_drawable() {
|
||||||
drawable
|
drawable
|
||||||
|
@ -124,20 +150,6 @@ impl MetalRenderer {
|
||||||
let command_buffer = command_queue.new_command_buffer();
|
let command_buffer = command_queue.new_command_buffer();
|
||||||
|
|
||||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
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
|
let color_attachment = render_pass_descriptor
|
||||||
.color_attachments()
|
.color_attachments()
|
||||||
.object_at(0)
|
.object_at(0)
|
||||||
|
@ -153,27 +165,57 @@ impl MetalRenderer {
|
||||||
command_encoder.set_viewport(metal::MTLViewport {
|
command_encoder.set_viewport(metal::MTLViewport {
|
||||||
originX: 0.0,
|
originX: 0.0,
|
||||||
originY: 0.0,
|
originY: 0.0,
|
||||||
width: u32::from(viewport_size.width) as f64,
|
width: i32::from(viewport_size.width) as f64,
|
||||||
height: u32::from(viewport_size.height) as f64,
|
height: i32::from(viewport_size.height) as f64,
|
||||||
znear: 0.0,
|
znear: 0.0,
|
||||||
zfar: 1.0,
|
zfar: 1.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut buffer_offset = 0;
|
let mut instance_offset = 0;
|
||||||
for layer in scene.layers() {
|
for layer in scene.layers() {
|
||||||
self.draw_quads(
|
for batch in layer.batches() {
|
||||||
&layer.quads,
|
match batch {
|
||||||
&mut buffer_offset,
|
crate::PrimitiveBatch::Quads(quads) => {
|
||||||
viewport_size,
|
self.draw_quads(
|
||||||
command_encoder,
|
quads,
|
||||||
);
|
&mut instance_offset,
|
||||||
|
viewport_size,
|
||||||
|
command_encoder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
crate::PrimitiveBatch::MonochromeSprites {
|
||||||
|
texture_id,
|
||||||
|
sprites,
|
||||||
|
} => {
|
||||||
|
self.draw_monochrome_sprites(
|
||||||
|
texture_id,
|
||||||
|
sprites,
|
||||||
|
&mut instance_offset,
|
||||||
|
viewport_size,
|
||||||
|
command_encoder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
crate::PrimitiveBatch::PolychromeSprites {
|
||||||
|
texture_id,
|
||||||
|
sprites,
|
||||||
|
} => {
|
||||||
|
self.draw_polychrome_sprites(
|
||||||
|
texture_id,
|
||||||
|
sprites,
|
||||||
|
&mut instance_offset,
|
||||||
|
viewport_size,
|
||||||
|
command_encoder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command_encoder.end_encoding();
|
command_encoder.end_encoding();
|
||||||
|
|
||||||
self.instances.did_modify_range(NSRange {
|
self.instances.did_modify_range(NSRange {
|
||||||
location: 0,
|
location: 0,
|
||||||
length: buffer_offset as NSUInteger,
|
length: instance_offset as NSUInteger,
|
||||||
});
|
});
|
||||||
|
|
||||||
command_buffer.commit();
|
command_buffer.commit();
|
||||||
|
@ -193,7 +235,7 @@ impl MetalRenderer {
|
||||||
}
|
}
|
||||||
align_offset(offset);
|
align_offset(offset);
|
||||||
|
|
||||||
command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
|
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
|
||||||
command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
QuadInputIndex::Vertices as u64,
|
QuadInputIndex::Vertices as u64,
|
||||||
Some(&self.unit_vertices),
|
Some(&self.unit_vertices),
|
||||||
|
@ -209,22 +251,20 @@ impl MetalRenderer {
|
||||||
Some(&self.instances),
|
Some(&self.instances),
|
||||||
*offset as u64,
|
*offset as u64,
|
||||||
);
|
);
|
||||||
let quad_uniforms = QuadUniforms { viewport_size };
|
|
||||||
|
|
||||||
let quad_uniform_bytes = bytemuck::bytes_of(&quad_uniforms);
|
|
||||||
command_encoder.set_vertex_bytes(
|
command_encoder.set_vertex_bytes(
|
||||||
QuadInputIndex::Uniforms as u64,
|
QuadInputIndex::ViewportSize as u64,
|
||||||
quad_uniform_bytes.len() as u64,
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
quad_uniform_bytes.as_ptr() as *const c_void,
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
);
|
);
|
||||||
|
|
||||||
let quad_bytes = bytemuck::cast_slice(quads);
|
let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
|
||||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
unsafe {
|
unsafe {
|
||||||
ptr::copy_nonoverlapping(quad_bytes.as_ptr(), buffer_contents, quad_bytes.len());
|
ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_offset = *offset + quad_bytes.len();
|
let next_offset = *offset + quad_bytes_len;
|
||||||
assert!(
|
assert!(
|
||||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
"instance buffer exhausted"
|
"instance buffer exhausted"
|
||||||
|
@ -238,6 +278,148 @@ impl MetalRenderer {
|
||||||
);
|
);
|
||||||
*offset = next_offset;
|
*offset = next_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_monochrome_sprites(
|
||||||
|
&mut self,
|
||||||
|
texture_id: AtlasTextureId,
|
||||||
|
sprites: &[MonochromeSprite],
|
||||||
|
offset: &mut usize,
|
||||||
|
viewport_size: Size<DevicePixels>,
|
||||||
|
command_encoder: &metal::RenderCommandEncoderRef,
|
||||||
|
) {
|
||||||
|
if sprites.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
align_offset(offset);
|
||||||
|
|
||||||
|
let texture = self.sprite_atlas.texture(texture_id);
|
||||||
|
let texture_size = size(
|
||||||
|
DevicePixels(texture.width() as i32),
|
||||||
|
DevicePixels(texture.height() as i32),
|
||||||
|
);
|
||||||
|
command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
SpriteInputIndex::Vertices as u64,
|
||||||
|
Some(&self.unit_vertices),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
SpriteInputIndex::Sprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
SpriteInputIndex::ViewportSize as u64,
|
||||||
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
SpriteInputIndex::AtlasTextureSize as u64,
|
||||||
|
mem::size_of_val(&texture_size) as u64,
|
||||||
|
&texture_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_buffer(
|
||||||
|
SpriteInputIndex::Sprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||||
|
|
||||||
|
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
|
||||||
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
sprites.as_ptr() as *const u8,
|
||||||
|
buffer_contents,
|
||||||
|
sprite_bytes_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_offset = *offset + sprite_bytes_len;
|
||||||
|
assert!(
|
||||||
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
|
"instance buffer exhausted"
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
sprites.len() as u64,
|
||||||
|
);
|
||||||
|
*offset = next_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_polychrome_sprites(
|
||||||
|
&mut self,
|
||||||
|
texture_id: AtlasTextureId,
|
||||||
|
sprites: &[PolychromeSprite],
|
||||||
|
offset: &mut usize,
|
||||||
|
viewport_size: Size<DevicePixels>,
|
||||||
|
command_encoder: &metal::RenderCommandEncoderRef,
|
||||||
|
) {
|
||||||
|
if sprites.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
align_offset(offset);
|
||||||
|
|
||||||
|
let texture = self.sprite_atlas.texture(texture_id);
|
||||||
|
let texture_size = size(
|
||||||
|
DevicePixels(texture.width() as i32),
|
||||||
|
DevicePixels(texture.height() as i32),
|
||||||
|
);
|
||||||
|
command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
SpriteInputIndex::Vertices as u64,
|
||||||
|
Some(&self.unit_vertices),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
SpriteInputIndex::Sprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
SpriteInputIndex::ViewportSize as u64,
|
||||||
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
SpriteInputIndex::AtlasTextureSize as u64,
|
||||||
|
mem::size_of_val(&texture_size) as u64,
|
||||||
|
&texture_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_buffer(
|
||||||
|
SpriteInputIndex::Sprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||||
|
|
||||||
|
let sprite_bytes_len = mem::size_of::<PolychromeSprite>() * sprites.len();
|
||||||
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
sprites.as_ptr() as *const u8,
|
||||||
|
buffer_contents,
|
||||||
|
sprite_bytes_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_offset = *offset + sprite_bytes_len;
|
||||||
|
assert!(
|
||||||
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
|
"instance buffer exhausted"
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
sprites.len() as u64,
|
||||||
|
);
|
||||||
|
*offset = next_offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_pipeline_state(
|
fn build_pipeline_state(
|
||||||
|
@ -268,7 +450,7 @@ fn build_pipeline_state(
|
||||||
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
|
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
|
||||||
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
|
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
|
||||||
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
|
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
|
||||||
// descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Depth32Float);
|
descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
|
||||||
|
|
||||||
device
|
device
|
||||||
.new_render_pipeline_state(&descriptor)
|
.new_render_pipeline_state(&descriptor)
|
||||||
|
@ -284,11 +466,14 @@ fn align_offset(offset: &mut usize) {
|
||||||
enum QuadInputIndex {
|
enum QuadInputIndex {
|
||||||
Vertices = 0,
|
Vertices = 0,
|
||||||
Quads = 1,
|
Quads = 1,
|
||||||
Uniforms = 2,
|
ViewportSize = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct QuadUniforms {
|
enum SpriteInputIndex {
|
||||||
viewport_size: Size<DevicePixels>,
|
Vertices = 0,
|
||||||
|
Sprites = 1,
|
||||||
|
ViewportSize = 2,
|
||||||
|
AtlasTextureSize = 3,
|
||||||
|
AtlasTexture = 4,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,177 +4,303 @@
|
||||||
using namespace metal;
|
using namespace metal;
|
||||||
|
|
||||||
float4 hsla_to_rgba(Hsla hsla);
|
float4 hsla_to_rgba(Hsla hsla);
|
||||||
float4 to_device_position(float2 pixel_position, float2 viewport_size);
|
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||||
|
Bounds_ScaledPixels clip_bounds,
|
||||||
|
constant Size_DevicePixels *viewport_size);
|
||||||
|
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||||
|
constant Size_DevicePixels *atlas_size);
|
||||||
|
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||||
|
Corners_ScaledPixels corner_radii);
|
||||||
|
|
||||||
struct QuadVertexOutput {
|
struct QuadVertexOutput {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
float4 background_color;
|
float4 background_color [[flat]];
|
||||||
float4 border_color;
|
float4 border_color [[flat]];
|
||||||
uint quad_id;
|
uint quad_id [[flat]];
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex QuadVertexOutput quad_vertex(
|
vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
||||||
uint unit_vertex_id [[vertex_id]],
|
uint quad_id [[instance_id]],
|
||||||
uint quad_id [[instance_id]],
|
constant float2 *unit_vertices
|
||||||
constant float2 *unit_vertices [[buffer(QuadInputIndex_Vertices)]],
|
[[buffer(QuadInputIndex_Vertices)]],
|
||||||
constant Quad *quads [[buffer(QuadInputIndex_Quads)]],
|
constant Quad *quads
|
||||||
constant QuadUniforms *uniforms [[buffer(QuadInputIndex_Uniforms)]]
|
[[buffer(QuadInputIndex_Quads)]],
|
||||||
) {
|
constant Size_DevicePixels *viewport_size
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
||||||
Quad quad = quads[quad_id];
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
float2 position_2d = unit_vertex * float2(quad.bounds.size.width, quad.bounds.size.height) + float2(quad.bounds.origin.x, quad.bounds.origin.y);
|
Quad quad = quads[quad_id];
|
||||||
position_2d.x = max(quad.clip_bounds.origin.x, position_2d.x);
|
float4 device_position = to_device_position(unit_vertex, quad.bounds,
|
||||||
position_2d.x = min(quad.clip_bounds.origin.x + quad.clip_bounds.size.width, position_2d.x);
|
quad.clip_bounds, viewport_size);
|
||||||
position_2d.y = max(quad.clip_bounds.origin.y, position_2d.y);
|
float4 background_color = hsla_to_rgba(quad.background);
|
||||||
position_2d.y = min(quad.clip_bounds.origin.y + quad.clip_bounds.size.height, position_2d.y);
|
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||||
|
return QuadVertexOutput{device_position, background_color, border_color,
|
||||||
float2 viewport_size = float2((float)uniforms->viewport_size.width, (float)uniforms->viewport_size.height);
|
quad_id};
|
||||||
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) {
|
fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
|
||||||
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
|
constant Quad *quads
|
||||||
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
|
[[buffer(QuadInputIndex_Quads)]]) {
|
||||||
float2 center_to_point = point - center;
|
Quad quad = quads[input.quad_id];
|
||||||
float corner_radius;
|
float2 half_size =
|
||||||
if (center_to_point.x < 0.) {
|
float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
|
||||||
if (center_to_point.y < 0.) {
|
float2 center =
|
||||||
corner_radius = corner_radii.top_left;
|
float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
|
||||||
} else {
|
float2 center_to_point = input.position.xy - center;
|
||||||
corner_radius = corner_radii.bottom_left;
|
float corner_radius;
|
||||||
}
|
if (center_to_point.x < 0.) {
|
||||||
|
if (center_to_point.y < 0.) {
|
||||||
|
corner_radius = quad.corner_radii.top_left;
|
||||||
} else {
|
} else {
|
||||||
if (center_to_point.y < 0.) {
|
corner_radius = quad.corner_radii.bottom_left;
|
||||||
corner_radius = corner_radii.top_right;
|
|
||||||
} else {
|
|
||||||
corner_radius = corner_radii.bottom_right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} 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 = abs(center_to_point) - half_size + corner_radius;
|
float2 rounded_edge_to_point =
|
||||||
float distance = length(max(0., rounded_edge_to_point))
|
fabs(center_to_point) - half_size + corner_radius;
|
||||||
+ min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y))
|
float distance =
|
||||||
- corner_radius;
|
length(max(0., rounded_edge_to_point)) +
|
||||||
|
min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
|
||||||
|
corner_radius;
|
||||||
|
|
||||||
return distance;
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 quad_fragment(
|
struct MonochromeSpriteVertexOutput {
|
||||||
QuadVertexOutput input [[stage_in]],
|
float4 position [[position]];
|
||||||
constant Quad *quads [[buffer(QuadInputIndex_Quads)]]
|
float2 tile_position;
|
||||||
) {
|
float4 color [[flat]];
|
||||||
Quad quad = quads[input.quad_id];
|
uint sprite_id [[flat]];
|
||||||
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;
|
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||||
float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
|
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||||
|
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||||
|
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
|
constant Size_DevicePixels *viewport_size
|
||||||
|
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||||
|
constant Size_DevicePixels *atlas_size
|
||||||
|
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||||
|
|
||||||
float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left : quad.border_widths.right;
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top : quad.border_widths.bottom;
|
MonochromeSprite sprite = sprites[sprite_id];
|
||||||
float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
|
float4 device_position = to_device_position(
|
||||||
float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
|
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
|
||||||
float border_width;
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
|
float4 color = hsla_to_rgba(sprite.color);
|
||||||
border_width = 0.;
|
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
|
||||||
} else if (point_to_inset_corner.y > point_to_inset_corner.x) {
|
sprite_id};
|
||||||
border_width = horizontal_border;
|
}
|
||||||
} else {
|
|
||||||
border_width = vertical_border;
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 color;
|
fragment float4 monochrome_sprite_fragment(
|
||||||
if (border_width == 0.) {
|
MonochromeSpriteVertexOutput input [[stage_in]],
|
||||||
color = input.background_color;
|
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
} else {
|
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||||
float inset_distance = distance + border_width;
|
MonochromeSprite sprite = sprites[input.sprite_id];
|
||||||
|
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||||
|
min_filter::linear);
|
||||||
|
float4 sample =
|
||||||
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
|
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||||
|
sprite.content_mask.corner_radii);
|
||||||
|
float4 color = input.color;
|
||||||
|
color.a *= sample.a * saturate(0.5 - clip_distance);
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
// Decrease border's opacity as we move inside the background.
|
struct PolychromeSpriteVertexOutput {
|
||||||
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
|
float4 position [[position]];
|
||||||
|
float2 tile_position;
|
||||||
|
uint sprite_id [[flat]];
|
||||||
|
};
|
||||||
|
|
||||||
// Alpha-blend the border and the background.
|
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
||||||
float output_alpha = quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
|
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||||
float3 premultiplied_border_rgb = input.border_color.rgb * quad.border_color.a;
|
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||||
float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
|
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
|
constant Size_DevicePixels *viewport_size
|
||||||
color = float4(premultiplied_output_rgb, output_alpha);
|
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||||
}
|
constant Size_DevicePixels *atlas_size
|
||||||
|
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||||
|
|
||||||
float clip_distance = quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii);
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
return color * float4(1., 1., 1., saturate(0.5 - distance) * saturate(0.5 - clip_distance));
|
PolychromeSprite sprite = sprites[sprite_id];
|
||||||
|
float4 device_position = to_device_position(
|
||||||
|
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
|
||||||
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
|
return PolychromeSpriteVertexOutput{device_position, tile_position,
|
||||||
|
sprite_id};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 polychrome_sprite_fragment(
|
||||||
|
PolychromeSpriteVertexOutput input [[stage_in]],
|
||||||
|
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
|
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||||
|
PolychromeSprite sprite = sprites[input.sprite_id];
|
||||||
|
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||||
|
min_filter::linear);
|
||||||
|
float4 sample =
|
||||||
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
|
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||||
|
sprite.content_mask.corner_radii);
|
||||||
|
float4 color = sample;
|
||||||
|
if (sprite.grayscale) {
|
||||||
|
float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
||||||
|
color.r = grayscale;
|
||||||
|
color.g = grayscale;
|
||||||
|
color.b = grayscale;
|
||||||
|
}
|
||||||
|
color.a *= saturate(0.5 - clip_distance);
|
||||||
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 hsla_to_rgba(Hsla hsla) {
|
float4 hsla_to_rgba(Hsla hsla) {
|
||||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||||
float s = hsla.s;
|
float s = hsla.s;
|
||||||
float l = hsla.l;
|
float l = hsla.l;
|
||||||
float a = hsla.a;
|
float a = hsla.a;
|
||||||
|
|
||||||
float c = (1.0 - fabs(2.0*l - 1.0)) * s;
|
float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
|
||||||
float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
|
float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
|
||||||
float m = l - c/2.0;
|
float m = l - c / 2.0;
|
||||||
|
|
||||||
float r = 0.0;
|
float r = 0.0;
|
||||||
float g = 0.0;
|
float g = 0.0;
|
||||||
float b = 0.0;
|
float b = 0.0;
|
||||||
|
|
||||||
if (h >= 0.0 && h < 1.0) {
|
if (h >= 0.0 && h < 1.0) {
|
||||||
r = c;
|
r = c;
|
||||||
g = x;
|
g = x;
|
||||||
b = 0.0;
|
b = 0.0;
|
||||||
} else if (h >= 1.0 && h < 2.0) {
|
} else if (h >= 1.0 && h < 2.0) {
|
||||||
r = x;
|
r = x;
|
||||||
g = c;
|
g = c;
|
||||||
b = 0.0;
|
b = 0.0;
|
||||||
} else if (h >= 2.0 && h < 3.0) {
|
} else if (h >= 2.0 && h < 3.0) {
|
||||||
r = 0.0;
|
r = 0.0;
|
||||||
g = c;
|
g = c;
|
||||||
b = x;
|
b = x;
|
||||||
} else if (h >= 3.0 && h < 4.0) {
|
} else if (h >= 3.0 && h < 4.0) {
|
||||||
r = 0.0;
|
r = 0.0;
|
||||||
g = x;
|
g = x;
|
||||||
b = c;
|
b = c;
|
||||||
} else if (h >= 4.0 && h < 5.0) {
|
} else if (h >= 4.0 && h < 5.0) {
|
||||||
r = x;
|
r = x;
|
||||||
g = 0.0;
|
g = 0.0;
|
||||||
b = c;
|
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 unit_vertex, Bounds_ScaledPixels bounds,
|
||||||
|
Bounds_ScaledPixels clip_bounds,
|
||||||
|
constant Size_DevicePixels *input_viewport_size) {
|
||||||
|
float2 position =
|
||||||
|
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||||
|
float2(bounds.origin.x, bounds.origin.y);
|
||||||
|
position.x = max(clip_bounds.origin.x, position.x);
|
||||||
|
position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
|
||||||
|
position.y = max(clip_bounds.origin.y, position.y);
|
||||||
|
position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
|
||||||
|
|
||||||
|
float2 viewport_size = float2((float)input_viewport_size->width,
|
||||||
|
(float)input_viewport_size->height);
|
||||||
|
float2 device_position =
|
||||||
|
position / viewport_size * float2(2., -2.) + float2(-1., 1.);
|
||||||
|
return float4(device_position, 0., 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||||
|
constant Size_DevicePixels *atlas_size) {
|
||||||
|
float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
|
||||||
|
float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
|
||||||
|
return (tile_origin + unit_vertex * tile_size) /
|
||||||
|
float2((float)atlas_size->width, (float)atlas_size->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||||
|
Corners_ScaledPixels 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 {
|
} else {
|
||||||
r = c;
|
corner_radius = corner_radii.bottom_left;
|
||||||
g = 0.0;
|
|
||||||
b = x;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (center_to_point.y < 0.) {
|
||||||
|
corner_radius = corner_radii.top_right;
|
||||||
|
} else {
|
||||||
|
corner_radius = corner_radii.bottom_right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float4 rgba;
|
float2 rounded_edge_to_point =
|
||||||
rgba.x = (r + m);
|
abs(center_to_point) - half_size + corner_radius;
|
||||||
rgba.y = (g + m);
|
float distance =
|
||||||
rgba.z = (b + m);
|
length(max(0., rounded_edge_to_point)) +
|
||||||
rgba.w = a;
|
min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
|
||||||
return rgba;
|
corner_radius;
|
||||||
}
|
|
||||||
|
|
||||||
float4 to_device_position(float2 pixel_position, float2 viewport_size) {
|
return distance;
|
||||||
return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
|
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
|
||||||
GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
|
FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph,
|
||||||
SharedString, Size,
|
ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
|
use anyhow::anyhow;
|
||||||
use cocoa::appkit::{CGFloat, CGPoint};
|
use cocoa::appkit::{CGFloat, CGPoint};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use core_foundation::{
|
use core_foundation::{
|
||||||
|
@ -137,23 +138,15 @@ impl PlatformTextSystem for MacTextSystem {
|
||||||
self.0.read().glyph_for_char(font_id, ch)
|
self.0.read().glyph_for_char(font_id, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||||
|
self.0.read().raster_bounds(params)
|
||||||
|
}
|
||||||
|
|
||||||
fn rasterize_glyph(
|
fn rasterize_glyph(
|
||||||
&self,
|
&self,
|
||||||
font_id: FontId,
|
glyph_id: &RenderGlyphParams,
|
||||||
font_size: f32,
|
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||||
glyph_id: GlyphId,
|
self.0.read().rasterize_glyph(glyph_id)
|
||||||
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(
|
fn layout_line(
|
||||||
|
@ -161,7 +154,7 @@ impl PlatformTextSystem for MacTextSystem {
|
||||||
text: &str,
|
text: &str,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
font_runs: &[(usize, FontId)],
|
font_runs: &[(usize, FontId)],
|
||||||
) -> LineLayout {
|
) -> ShapedLine {
|
||||||
self.0.write().layout_line(text, font_size, font_runs)
|
self.0.write().layout_line(text, font_size, font_runs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,90 +238,91 @@ impl MacTextSystemState {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rasterize_glyph(
|
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||||
&self,
|
let font = &self.fonts[params.font_id.0];
|
||||||
font_id: FontId,
|
let scale = Transform2F::from_scale(params.scale_factor);
|
||||||
font_size: f32,
|
Ok(font
|
||||||
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(
|
.raster_bounds(
|
||||||
glyph_id.into(),
|
params.glyph_id.into(),
|
||||||
font_size,
|
params.font_size.into(),
|
||||||
scale,
|
scale,
|
||||||
HintingOptions::None,
|
HintingOptions::None,
|
||||||
font_kit::canvas::RasterizationOptions::GrayscaleAa,
|
font_kit::canvas::RasterizationOptions::GrayscaleAa,
|
||||||
)
|
)?
|
||||||
.ok()?;
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
|
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||||
None
|
let glyph_bounds = self.raster_bounds(params)?;
|
||||||
|
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
|
||||||
|
Err(anyhow!("glyph bounds are empty"))
|
||||||
} else {
|
} else {
|
||||||
// Make room for subpixel variants.
|
// Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
|
||||||
let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
|
let mut bitmap_size = glyph_bounds.size;
|
||||||
let cx_bounds = RectI::new(
|
if params.subpixel_variant.x > 0 {
|
||||||
glyph_bounds.origin(),
|
bitmap_size.width += DevicePixels(1);
|
||||||
glyph_bounds.size() + Vector2I::from(subpixel_padding),
|
}
|
||||||
);
|
if params.subpixel_variant.y > 0 {
|
||||||
|
bitmap_size.height += DevicePixels(1);
|
||||||
|
}
|
||||||
|
|
||||||
let mut bytes;
|
let mut bytes;
|
||||||
let cx;
|
let cx;
|
||||||
match options {
|
if params.is_emoji {
|
||||||
RasterizationOptions::Alpha => {
|
bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];
|
||||||
bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
|
cx = CGContext::create_bitmap_context(
|
||||||
cx = CGContext::create_bitmap_context(
|
Some(bytes.as_mut_ptr() as *mut _),
|
||||||
Some(bytes.as_mut_ptr() as *mut _),
|
bitmap_size.width.0 as usize,
|
||||||
cx_bounds.width() as usize,
|
bitmap_size.height.0 as usize,
|
||||||
cx_bounds.height() as usize,
|
8,
|
||||||
8,
|
bitmap_size.width.0 as usize * 4,
|
||||||
cx_bounds.width() as usize,
|
&CGColorSpace::create_device_rgb(),
|
||||||
&CGColorSpace::create_device_gray(),
|
kCGImageAlphaPremultipliedLast,
|
||||||
kCGImageAlphaOnly,
|
);
|
||||||
);
|
} else {
|
||||||
}
|
bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
|
||||||
RasterizationOptions::Bgra => {
|
cx = CGContext::create_bitmap_context(
|
||||||
bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
|
Some(bytes.as_mut_ptr() as *mut _),
|
||||||
cx = CGContext::create_bitmap_context(
|
bitmap_size.width.0 as usize,
|
||||||
Some(bytes.as_mut_ptr() as *mut _),
|
bitmap_size.height.0 as usize,
|
||||||
cx_bounds.width() as usize,
|
8,
|
||||||
cx_bounds.height() as usize,
|
bitmap_size.width.0 as usize,
|
||||||
8,
|
&CGColorSpace::create_device_gray(),
|
||||||
cx_bounds.width() as usize * 4,
|
kCGImageAlphaOnly,
|
||||||
&CGColorSpace::create_device_rgb(),
|
);
|
||||||
kCGImageAlphaPremultipliedLast,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the origin to bottom left and account for scaling, this
|
// Move the origin to bottom left and account for scaling, this
|
||||||
// makes drawing text consistent with the font-kit's raster_bounds.
|
// makes drawing text consistent with the font-kit's raster_bounds.
|
||||||
cx.translate(
|
cx.translate(
|
||||||
-glyph_bounds.origin_x() as CGFloat,
|
-glyph_bounds.origin.x.0 as CGFloat,
|
||||||
(glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
|
(glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
|
||||||
|
);
|
||||||
|
cx.scale(
|
||||||
|
params.scale_factor as CGFloat,
|
||||||
|
params.scale_factor as CGFloat,
|
||||||
);
|
);
|
||||||
cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
|
|
||||||
|
|
||||||
|
let subpixel_shift = params
|
||||||
|
.subpixel_variant
|
||||||
|
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
|
||||||
cx.set_allows_font_subpixel_positioning(true);
|
cx.set_allows_font_subpixel_positioning(true);
|
||||||
cx.set_should_subpixel_position_fonts(true);
|
cx.set_should_subpixel_position_fonts(true);
|
||||||
cx.set_allows_font_subpixel_quantization(false);
|
cx.set_allows_font_subpixel_quantization(false);
|
||||||
cx.set_should_subpixel_quantize_fonts(false);
|
cx.set_should_subpixel_quantize_fonts(false);
|
||||||
font.native_font()
|
self.fonts[params.font_id.0]
|
||||||
.clone_with_font_size(font_size as CGFloat)
|
.native_font()
|
||||||
|
.clone_with_font_size(f32::from(params.font_size) as CGFloat)
|
||||||
.draw_glyphs(
|
.draw_glyphs(
|
||||||
&[u32::from(glyph_id) as CGGlyph],
|
&[u32::from(params.glyph_id) as CGGlyph],
|
||||||
&[CGPoint::new(
|
&[CGPoint::new(
|
||||||
(f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
|
(subpixel_shift.x / params.scale_factor) as CGFloat,
|
||||||
(f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
|
(subpixel_shift.y / params.scale_factor) as CGFloat,
|
||||||
)],
|
)],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let RasterizationOptions::Bgra = options {
|
if params.is_emoji {
|
||||||
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
|
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
|
||||||
for pixel in bytes.chunks_exact_mut(4) {
|
for pixel in bytes.chunks_exact_mut(4) {
|
||||||
pixel.swap(0, 2);
|
pixel.swap(0, 2);
|
||||||
|
@ -339,7 +333,7 @@ impl MacTextSystemState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((cx_bounds.into(), bytes))
|
Ok((bitmap_size.into(), bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +342,7 @@ impl MacTextSystemState {
|
||||||
text: &str,
|
text: &str,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
font_runs: &[(usize, FontId)],
|
font_runs: &[(usize, FontId)],
|
||||||
) -> LineLayout {
|
) -> ShapedLine {
|
||||||
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
||||||
let mut string = CFMutableAttributedString::new();
|
let mut string = CFMutableAttributedString::new();
|
||||||
{
|
{
|
||||||
|
@ -409,7 +403,7 @@ impl MacTextSystemState {
|
||||||
{
|
{
|
||||||
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
|
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
|
||||||
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
|
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
|
||||||
glyphs.push(Glyph {
|
glyphs.push(ShapedGlyph {
|
||||||
id: (*glyph_id).into(),
|
id: (*glyph_id).into(),
|
||||||
position: point(position.x as f32, position.y as f32).map(px),
|
position: point(position.x as f32, position.y as f32).map(px),
|
||||||
index: ix_converter.utf8_ix,
|
index: ix_converter.utf8_ix,
|
||||||
|
@ -417,11 +411,11 @@ impl MacTextSystemState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runs.push(Run { font_id, glyphs })
|
runs.push(ShapedRun { font_id, glyphs })
|
||||||
}
|
}
|
||||||
|
|
||||||
let typographic_bounds = line.get_typographic_bounds();
|
let typographic_bounds = line.get_typographic_bounds();
|
||||||
LineLayout {
|
ShapedLine {
|
||||||
width: typographic_bounds.width.into(),
|
width: typographic_bounds.width.into(),
|
||||||
ascent: typographic_bounds.ascent.into(),
|
ascent: typographic_bounds.ascent.into(),
|
||||||
descent: typographic_bounds.descent.into(),
|
descent: typographic_bounds.descent.into(),
|
||||||
|
@ -549,11 +543,26 @@ impl From<RectF> for Bounds<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RectI> for Bounds<u32> {
|
impl From<RectI> for Bounds<DevicePixels> {
|
||||||
fn from(rect: RectI) -> Self {
|
fn from(rect: RectI) -> Self {
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
|
origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
|
||||||
size: size(rect.width() as u32, rect.height() as u32),
|
size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector2I> for Size<DevicePixels> {
|
||||||
|
fn from(value: Vector2I) -> Self {
|
||||||
|
size(value.x().into(), value.y().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RectI> for Bounds<i32> {
|
||||||
|
fn from(rect: RectI) -> Self {
|
||||||
|
Bounds {
|
||||||
|
origin: point(rect.origin_x(), rect.origin_y()),
|
||||||
|
size: size(rect.width(), rect.height()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{ns_string, MetalRenderer, NSRange};
|
use super::{ns_string, MetalRenderer, NSRange};
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke,
|
point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers,
|
||||||
MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt,
|
||||||
MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow,
|
Pixels, Platform, PlatformAtlas, PlatformDispatcher, PlatformInputHandler, PlatformScreen,
|
||||||
Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
PlatformWindow, Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
|
||||||
WindowPromptLevel,
|
WindowOptions, WindowPromptLevel,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -292,7 +292,7 @@ struct MacWindowState {
|
||||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||||
close_callback: Option<Box<dyn FnOnce()>>,
|
close_callback: Option<Box<dyn FnOnce()>>,
|
||||||
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
||||||
input_handler: Option<Box<dyn InputHandler>>,
|
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||||
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
|
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
|
||||||
last_key_equivalent: Option<KeyDownEvent>,
|
last_key_equivalent: Option<KeyDownEvent>,
|
||||||
synthetic_drag_counter: usize,
|
synthetic_drag_counter: usize,
|
||||||
|
@ -671,7 +671,7 @@ impl PlatformWindow for MacWindow {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
|
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
|
||||||
self.0.as_ref().lock().input_handler = Some(input_handler);
|
self.0.as_ref().lock().input_handler = Some(input_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,6 +885,10 @@ impl PlatformWindow for MacWindow {
|
||||||
let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
|
let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||||
|
self.0.lock().renderer.sprite_atlas().clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_scale_factor(native_window: id) -> f32 {
|
fn get_scale_factor(native_window: id) -> f32 {
|
||||||
|
@ -1357,9 +1361,8 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let window_state = get_window_state(this);
|
let window_state = get_window_state(this);
|
||||||
let mut window_state = window_state.as_ref().lock();
|
let mut window_state = window_state.as_ref().lock();
|
||||||
if let Some(scene) = window_state.scene_to_render.take() {
|
if let Some(mut scene) = window_state.scene_to_render.take() {
|
||||||
dbg!("render", &scene);
|
window_state.renderer.draw(&mut scene);
|
||||||
window_state.renderer.draw(&scene);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1580,7 +1583,7 @@ async fn synthetic_drag(
|
||||||
|
|
||||||
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
|
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut dyn InputHandler) -> R,
|
F: FnOnce(&mut dyn PlatformInputHandler) -> R,
|
||||||
{
|
{
|
||||||
let window_state = unsafe { get_window_state(window) };
|
let window_state = unsafe { get_window_state(window) };
|
||||||
let mut lock = window_state.as_ref().lock();
|
let mut lock = window_state.as_ref().lock();
|
||||||
|
|
|
@ -1,97 +1,211 @@
|
||||||
use std::mem;
|
use std::{iter::Peekable, mem, slice};
|
||||||
|
|
||||||
use super::{Bounds, Hsla, Pixels, Point};
|
use super::{Bounds, Hsla, Point};
|
||||||
use crate::{Corners, Edges};
|
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
|
||||||
use bytemuck::{Pod, Zeroable};
|
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
// Exported to metal
|
// Exported to metal
|
||||||
pub type PointF = Point<f32>;
|
pub type PointF = Point<f32>;
|
||||||
|
pub type LayerId = SmallVec<[u32; 16]>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
layers: BTreeMap<u32, SceneLayer>,
|
|
||||||
pub(crate) scale_factor: f32,
|
pub(crate) scale_factor: f32,
|
||||||
}
|
pub(crate) layers: BTreeMap<LayerId, SceneLayer>,
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct SceneLayer {
|
|
||||||
pub quads: Vec<Quad>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn new(scale_factor: f32) -> Scene {
|
pub fn new(scale_factor: f32) -> Scene {
|
||||||
Scene {
|
Scene {
|
||||||
layers: Default::default(),
|
|
||||||
scale_factor,
|
scale_factor,
|
||||||
|
layers: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take(&mut self) -> Scene {
|
pub fn take(&mut self) -> Scene {
|
||||||
Scene {
|
Scene {
|
||||||
layers: mem::take(&mut self.layers),
|
|
||||||
scale_factor: self.scale_factor,
|
scale_factor: self.scale_factor,
|
||||||
|
layers: mem::take(&mut self.layers),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, primitive: impl Into<Primitive>) {
|
pub fn insert(&mut self, stacking_order: LayerId, primitive: impl Into<Primitive>) {
|
||||||
let mut primitive = primitive.into();
|
let layer = self.layers.entry(stacking_order).or_default();
|
||||||
primitive.scale(self.scale_factor);
|
|
||||||
let layer = self.layers.entry(primitive.order()).or_default();
|
let primitive = primitive.into();
|
||||||
match primitive {
|
match primitive {
|
||||||
Primitive::Quad(quad) => layer.quads.push(quad),
|
Primitive::Quad(quad) => {
|
||||||
|
layer.quads.push(quad);
|
||||||
|
}
|
||||||
|
Primitive::MonochromeSprite(sprite) => {
|
||||||
|
layer.monochrome_sprites.push(sprite);
|
||||||
|
}
|
||||||
|
Primitive::PolychromeSprite(sprite) => {
|
||||||
|
layer.polychrome_sprites.push(sprite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layers(&self) -> impl Iterator<Item = &SceneLayer> {
|
pub(crate) fn layers(&mut self) -> impl Iterator<Item = &mut SceneLayer> {
|
||||||
self.layers.values()
|
self.layers.values_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct SceneLayer {
|
||||||
|
pub quads: Vec<Quad>,
|
||||||
|
pub monochrome_sprites: Vec<MonochromeSprite>,
|
||||||
|
pub polychrome_sprites: Vec<PolychromeSprite>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SceneLayer {
|
||||||
|
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||||
|
self.quads.sort_unstable();
|
||||||
|
self.monochrome_sprites.sort_unstable();
|
||||||
|
self.polychrome_sprites.sort_unstable();
|
||||||
|
BatchIterator {
|
||||||
|
quads: &self.quads,
|
||||||
|
quads_start: 0,
|
||||||
|
quads_iter: self.quads.iter().peekable(),
|
||||||
|
monochrome_sprites: &self.monochrome_sprites,
|
||||||
|
monochrome_sprites_start: 0,
|
||||||
|
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
|
||||||
|
polychrome_sprites: &self.polychrome_sprites,
|
||||||
|
polychrome_sprites_start: 0,
|
||||||
|
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BatchIterator<'a> {
|
||||||
|
quads: &'a [Quad],
|
||||||
|
quads_start: usize,
|
||||||
|
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
||||||
|
monochrome_sprites: &'a [MonochromeSprite],
|
||||||
|
monochrome_sprites_start: usize,
|
||||||
|
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
|
||||||
|
polychrome_sprites: &'a [PolychromeSprite],
|
||||||
|
polychrome_sprites_start: usize,
|
||||||
|
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for BatchIterator<'a> {
|
||||||
|
type Item = PrimitiveBatch<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let mut kinds_and_orders = [
|
||||||
|
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
|
||||||
|
(
|
||||||
|
PrimitiveKind::MonochromeSprite,
|
||||||
|
self.monochrome_sprites_iter.peek().map(|s| s.order),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PrimitiveKind::PolychromeSprite,
|
||||||
|
self.polychrome_sprites_iter.peek().map(|s| s.order),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX));
|
||||||
|
|
||||||
|
let first = kinds_and_orders[0];
|
||||||
|
let second = kinds_and_orders[1];
|
||||||
|
let (batch_kind, max_order) = if first.1.is_some() {
|
||||||
|
(first.0, second.1.unwrap_or(u32::MAX))
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
match batch_kind {
|
||||||
|
PrimitiveKind::Quad => {
|
||||||
|
let quads_start = self.quads_start;
|
||||||
|
let quads_end = quads_start
|
||||||
|
+ self
|
||||||
|
.quads_iter
|
||||||
|
.by_ref()
|
||||||
|
.take_while(|quad| quad.order <= max_order)
|
||||||
|
.count();
|
||||||
|
self.quads_start = quads_end;
|
||||||
|
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
|
||||||
|
}
|
||||||
|
PrimitiveKind::MonochromeSprite => {
|
||||||
|
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||||
|
let sprites_start = self.monochrome_sprites_start;
|
||||||
|
let sprites_end = sprites_start
|
||||||
|
+ self
|
||||||
|
.monochrome_sprites_iter
|
||||||
|
.by_ref()
|
||||||
|
.take_while(|sprite| {
|
||||||
|
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
self.monochrome_sprites_start = sprites_end;
|
||||||
|
Some(PrimitiveBatch::MonochromeSprites {
|
||||||
|
texture_id,
|
||||||
|
sprites: &self.monochrome_sprites[sprites_start..sprites_end],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
PrimitiveKind::PolychromeSprite => {
|
||||||
|
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||||
|
let sprites_start = self.polychrome_sprites_start;
|
||||||
|
let sprites_end = sprites_start
|
||||||
|
+ self
|
||||||
|
.polychrome_sprites_iter
|
||||||
|
.by_ref()
|
||||||
|
.take_while(|sprite| {
|
||||||
|
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
self.polychrome_sprites_start = sprites_end;
|
||||||
|
Some(PrimitiveBatch::PolychromeSprites {
|
||||||
|
texture_id,
|
||||||
|
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum PrimitiveKind {
|
||||||
|
Quad,
|
||||||
|
MonochromeSprite,
|
||||||
|
PolychromeSprite,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Primitive {
|
pub enum Primitive {
|
||||||
Quad(Quad),
|
Quad(Quad),
|
||||||
|
MonochromeSprite(MonochromeSprite),
|
||||||
|
PolychromeSprite(PolychromeSprite),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive {
|
pub(crate) enum PrimitiveBatch<'a> {
|
||||||
pub fn order(&self) -> u32 {
|
Quads(&'a [Quad]),
|
||||||
match self {
|
MonochromeSprites {
|
||||||
Primitive::Quad(quad) => quad.order,
|
texture_id: AtlasTextureId,
|
||||||
}
|
sprites: &'a [MonochromeSprite],
|
||||||
}
|
},
|
||||||
|
PolychromeSprites {
|
||||||
pub fn is_transparent(&self) -> bool {
|
texture_id: AtlasTextureId,
|
||||||
match self {
|
sprites: &'a [PolychromeSprite],
|
||||||
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Quad {
|
pub struct Quad {
|
||||||
pub order: u32,
|
pub order: u32,
|
||||||
pub bounds: Bounds<Pixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub clip_bounds: Bounds<Pixels>,
|
pub clip_bounds: Bounds<ScaledPixels>,
|
||||||
pub clip_corner_radii: Corners<Pixels>,
|
pub clip_corner_radii: Corners<ScaledPixels>,
|
||||||
pub background: Hsla,
|
pub background: Hsla,
|
||||||
pub border_color: Hsla,
|
pub border_color: Hsla,
|
||||||
pub corner_radii: Corners<Pixels>,
|
pub corner_radii: Corners<ScaledPixels>,
|
||||||
pub border_widths: Edges<Pixels>,
|
pub border_widths: Edges<ScaledPixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quad {
|
impl Quad {
|
||||||
pub fn vertices(&self) -> impl Iterator<Item = Point<Pixels>> {
|
pub fn vertices(&self) -> impl Iterator<Item = Point<ScaledPixels>> {
|
||||||
let x1 = self.bounds.origin.x;
|
let x1 = self.bounds.origin.x;
|
||||||
let y1 = self.bounds.origin.y;
|
let y1 = self.bounds.origin.y;
|
||||||
let x2 = x1 + self.bounds.size.width;
|
let x2 = x1 + self.bounds.size.width;
|
||||||
|
@ -104,13 +218,17 @@ impl Quad {
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scale(&mut self, factor: f32) {
|
impl Ord for Quad {
|
||||||
self.bounds *= factor;
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
self.clip_bounds *= factor;
|
self.order.cmp(&other.order)
|
||||||
self.clip_corner_radii *= factor;
|
}
|
||||||
self.corner_radii *= factor;
|
}
|
||||||
self.border_widths *= factor;
|
|
||||||
|
impl PartialOrd for Quad {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,3 +237,68 @@ impl From<Quad> for Primitive {
|
||||||
Primitive::Quad(quad)
|
Primitive::Quad(quad)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct MonochromeSprite {
|
||||||
|
pub order: u32,
|
||||||
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
|
pub content_mask: ScaledContentMask,
|
||||||
|
pub color: Hsla,
|
||||||
|
pub tile: AtlasTile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for MonochromeSprite {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
match self.order.cmp(&other.order) {
|
||||||
|
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
|
||||||
|
order => order,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for MonochromeSprite {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MonochromeSprite> for Primitive {
|
||||||
|
fn from(sprite: MonochromeSprite) -> Self {
|
||||||
|
Primitive::MonochromeSprite(sprite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct PolychromeSprite {
|
||||||
|
pub order: u32,
|
||||||
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
|
pub content_mask: ScaledContentMask,
|
||||||
|
pub tile: AtlasTile,
|
||||||
|
pub grayscale: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for PolychromeSprite {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
match self.order.cmp(&other.order) {
|
||||||
|
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
|
||||||
|
order => order,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for PolychromeSprite {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PolychromeSprite> for Primitive {
|
||||||
|
fn from(sprite: PolychromeSprite) -> Self {
|
||||||
|
Primitive::PolychromeSprite(sprite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct AtlasId(pub(crate) usize);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
phi, rems, AbsoluteLength, Bounds, Corners, CornersRefinement, DefiniteLength, Edges,
|
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
|
||||||
EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point,
|
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
|
||||||
PointRefinement, Quad, Rems, Result, RunStyle, SharedString, Size, SizeRefinement, ViewContext,
|
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle,
|
||||||
WindowContext,
|
SharedString, Size, SizeRefinement, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
pub use taffy::style::{
|
pub use taffy::style::{
|
||||||
|
@ -179,22 +179,84 @@ impl Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
|
||||||
|
where
|
||||||
|
C: BorrowAppContext,
|
||||||
|
F: FnOnce(&mut C) -> R,
|
||||||
|
{
|
||||||
|
if self.text.is_some() {
|
||||||
|
cx.with_text_style(self.text.clone(), f)
|
||||||
|
} else {
|
||||||
|
f(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply overflow to content mask
|
||||||
|
pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
|
||||||
|
where
|
||||||
|
C: BorrowWindow,
|
||||||
|
F: FnOnce(&mut C) -> R,
|
||||||
|
{
|
||||||
|
let current_mask = cx.content_mask();
|
||||||
|
|
||||||
|
let min = current_mask.bounds.origin;
|
||||||
|
let max = current_mask.bounds.lower_right();
|
||||||
|
|
||||||
|
let mask_corner_radii = Corners::default();
|
||||||
|
let mask_bounds = match (
|
||||||
|
self.overflow.x == Overflow::Visible,
|
||||||
|
self.overflow.y == Overflow::Visible,
|
||||||
|
) {
|
||||||
|
// x and y both visible
|
||||||
|
(true, true) => return f(cx),
|
||||||
|
// x visible, y hidden
|
||||||
|
(true, false) => Bounds::from_corners(
|
||||||
|
point(min.x, bounds.origin.y),
|
||||||
|
point(max.x, bounds.lower_right().y),
|
||||||
|
),
|
||||||
|
// x hidden, y visible
|
||||||
|
(false, true) => Bounds::from_corners(
|
||||||
|
point(bounds.origin.x, min.y),
|
||||||
|
point(bounds.lower_right().x, max.y),
|
||||||
|
),
|
||||||
|
// both hidden
|
||||||
|
(false, false) => bounds,
|
||||||
|
};
|
||||||
|
let mask = ContentMask {
|
||||||
|
bounds: mask_bounds,
|
||||||
|
corner_radii: mask_corner_radii,
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.with_content_mask(mask, f)
|
||||||
|
}
|
||||||
|
|
||||||
/// Paints the background of an element styled with this style.
|
/// 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>) {
|
pub fn paint<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||||
let rem_size = cx.rem_size();
|
let rem_size = cx.rem_size();
|
||||||
|
let scale = cx.scale_factor();
|
||||||
|
|
||||||
let background_color = self.fill.as_ref().and_then(Fill::color);
|
let background_color = self.fill.as_ref().and_then(Fill::color);
|
||||||
if background_color.is_some() || self.is_border_visible() {
|
if background_color.is_some() || self.is_border_visible() {
|
||||||
cx.scene().insert(Quad {
|
let layer_id = cx.current_layer_id();
|
||||||
order,
|
cx.scene().insert(
|
||||||
bounds,
|
layer_id,
|
||||||
clip_bounds: bounds, // todo!
|
Quad {
|
||||||
clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
|
order,
|
||||||
background: background_color.unwrap_or_default(),
|
bounds: bounds.scale(scale),
|
||||||
border_color: self.border_color.unwrap_or_default(),
|
clip_bounds: bounds.scale(scale), // todo!
|
||||||
corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
|
clip_corner_radii: self
|
||||||
border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
|
.corner_radii
|
||||||
});
|
.map(|length| length.to_pixels(rem_size).scale(scale)),
|
||||||
|
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).scale(scale)),
|
||||||
|
border_widths: self
|
||||||
|
.border_widths
|
||||||
|
.map(|length| length.to_pixels(rem_size).scale(scale)),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
crates/gpui3/src/svg_renderer.rs
Normal file
47
crates/gpui3/src/svg_renderer.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Hash, Eq)]
|
||||||
|
pub struct RenderSvgParams {
|
||||||
|
pub(crate) path: SharedString,
|
||||||
|
pub(crate) size: Size<DevicePixels>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SvgRenderer {
|
||||||
|
asset_source: Arc<dyn AssetSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgRenderer {
|
||||||
|
pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
|
||||||
|
Self { asset_source }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self, params: &RenderSvgParams) -> Result<Vec<u8>> {
|
||||||
|
if params.size.is_zero() {
|
||||||
|
return Err(anyhow!("can't render at a zero size"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the tree.
|
||||||
|
let bytes = self.asset_source.load(¶ms.path)?;
|
||||||
|
let tree = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
|
||||||
|
|
||||||
|
// Render the SVG to a pixmap with the specified width and height.
|
||||||
|
let mut pixmap =
|
||||||
|
tiny_skia::Pixmap::new(params.size.width.into(), params.size.height.into()).unwrap();
|
||||||
|
resvg::render(
|
||||||
|
&tree,
|
||||||
|
usvg::FitTo::Width(params.size.width.into()),
|
||||||
|
pixmap.as_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert the pixmap's pixels into an alpha mask.
|
||||||
|
let alpha_mask = pixmap
|
||||||
|
.pixels()
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.alpha())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(alpha_mask)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
mod font_features;
|
mod font_features;
|
||||||
|
mod line;
|
||||||
mod line_wrapper;
|
mod line_wrapper;
|
||||||
mod text_layout_cache;
|
mod text_layout_cache;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
pub use font_features::*;
|
pub use font_features::*;
|
||||||
|
pub use line::*;
|
||||||
use line_wrapper::*;
|
use line_wrapper::*;
|
||||||
pub use text_layout_cache::*;
|
pub use text_layout_cache::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
|
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
|
||||||
|
UnderlineStyle,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
@ -21,17 +24,19 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct FontId(pub usize);
|
pub struct FontId(pub usize);
|
||||||
|
|
||||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
pub struct FontFamilyId(pub usize);
|
pub struct FontFamilyId(pub usize);
|
||||||
|
|
||||||
|
pub const SUBPIXEL_VARIANTS: u8 = 4;
|
||||||
|
|
||||||
pub struct TextSystem {
|
pub struct TextSystem {
|
||||||
text_layout_cache: Arc<TextLayoutCache>,
|
text_layout_cache: Arc<TextLayoutCache>,
|
||||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||||
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
||||||
fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
|
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
|
||||||
font_metrics: RwLock<HashMap<Font, FontMetrics>>,
|
|
||||||
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
||||||
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
|
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +48,6 @@ impl TextSystem {
|
||||||
platform_text_system,
|
platform_text_system,
|
||||||
font_metrics: RwLock::new(HashMap::default()),
|
font_metrics: RwLock::new(HashMap::default()),
|
||||||
font_ids_by_font: 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()),
|
wrapper_pool: Mutex::new(HashMap::default()),
|
||||||
font_runs_pool: Default::default(),
|
font_runs_pool: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -51,36 +55,25 @@ impl TextSystem {
|
||||||
|
|
||||||
pub fn font_id(&self, font: &Font) -> Result<FontId> {
|
pub fn font_id(&self, font: &Font) -> Result<FontId> {
|
||||||
let font_id = self.font_ids_by_font.read().get(font).copied();
|
let font_id = self.font_ids_by_font.read().get(font).copied();
|
||||||
|
|
||||||
if let Some(font_id) = font_id {
|
if let Some(font_id) = font_id {
|
||||||
Ok(font_id)
|
Ok(font_id)
|
||||||
} else {
|
} else {
|
||||||
let font_id = self.platform_text_system.font_id(font)?;
|
let font_id = self.platform_text_system.font_id(font)?;
|
||||||
self.font_ids_by_font.write().insert(font.clone(), font_id);
|
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)
|
Ok(font_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
|
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result<Bounds<Pixels>> {
|
||||||
self.fonts_by_font_id
|
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
|
||||||
.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(
|
pub fn typographic_bounds(
|
||||||
&self,
|
&self,
|
||||||
font: &Font,
|
font_id: FontId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
character: char,
|
character: char,
|
||||||
) -> Result<Bounds<Pixels>> {
|
) -> Result<Bounds<Pixels>> {
|
||||||
let font_id = self.font_id(font)?;
|
|
||||||
let glyph_id = self
|
let glyph_id = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.glyph_for_char(font_id, character)
|
.glyph_for_char(font_id, character)
|
||||||
|
@ -88,65 +81,63 @@ impl TextSystem {
|
||||||
let bounds = self
|
let bounds = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.typographic_bounds(font_id, glyph_id)?;
|
.typographic_bounds(font_id, glyph_id)?;
|
||||||
self.read_metrics(font, |metrics| {
|
self.read_metrics(font_id, |metrics| {
|
||||||
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
|
(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>> {
|
pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
|
||||||
let font_id = self.font_id(font)?;
|
|
||||||
let glyph_id = self
|
let glyph_id = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.glyph_for_char(font_id, ch)
|
.glyph_for_char(font_id, ch)
|
||||||
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
||||||
let result =
|
let result = self.platform_text_system.advance(font_id, glyph_id)?
|
||||||
self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32;
|
/ self.units_per_em(font_id)? as f32;
|
||||||
|
|
||||||
Ok(result * font_size)
|
Ok(result * font_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn units_per_em(&self, font: &Font) -> Result<u32> {
|
pub fn units_per_em(&self, font_id: FontId) -> Result<u32> {
|
||||||
self.read_metrics(font, |metrics| metrics.units_per_em as u32)
|
self.read_metrics(font_id, |metrics| metrics.units_per_em as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.cap_height(font_size))
|
self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.x_height(font_size))
|
self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.ascent(font_size))
|
self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.descent(font_size))
|
self.read_metrics(font_id, |metrics| metrics.descent(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn baseline_offset(
|
pub fn baseline_offset(
|
||||||
&self,
|
&self,
|
||||||
font: &Font,
|
font_id: FontId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
) -> Result<Pixels> {
|
) -> Result<Pixels> {
|
||||||
let ascent = self.ascent(font, font_size)?;
|
let ascent = self.ascent(font_id, font_size)?;
|
||||||
let descent = self.descent(font, font_size)?;
|
let descent = self.descent(font_id, font_size)?;
|
||||||
let padding_top = (line_height - ascent - descent) / 2.;
|
let padding_top = (line_height - ascent - descent) / 2.;
|
||||||
Ok(padding_top + ascent)
|
Ok(padding_top + ascent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
|
fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
|
||||||
let lock = self.font_metrics.upgradable_read();
|
let lock = self.font_metrics.upgradable_read();
|
||||||
|
|
||||||
if let Some(metrics) = lock.get(font) {
|
if let Some(metrics) = lock.get(&font_id) {
|
||||||
Ok(read(metrics))
|
Ok(read(metrics))
|
||||||
} else {
|
} else {
|
||||||
let font_id = self.platform_text_system.font_id(&font)?;
|
|
||||||
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
||||||
let metrics = lock
|
let metrics = lock
|
||||||
.entry(font.clone())
|
.entry(font_id)
|
||||||
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
|
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
|
||||||
Ok(read(metrics))
|
Ok(read(metrics))
|
||||||
}
|
}
|
||||||
|
@ -160,29 +151,18 @@ impl TextSystem {
|
||||||
) -> Result<Line> {
|
) -> Result<Line> {
|
||||||
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
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;
|
let mut last_font: Option<&Font> = None;
|
||||||
for (len, style) in runs {
|
for (len, style) in runs {
|
||||||
dbg!(len);
|
|
||||||
if let Some(last_font) = last_font.as_ref() {
|
if let Some(last_font) = last_font.as_ref() {
|
||||||
dbg!("a");
|
|
||||||
if **last_font == style.font {
|
if **last_font == style.font {
|
||||||
dbg!("b");
|
|
||||||
font_runs.last_mut().unwrap().0 += len;
|
font_runs.last_mut().unwrap().0 += len;
|
||||||
dbg!("c");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
dbg!("d");
|
|
||||||
}
|
}
|
||||||
dbg!("e");
|
|
||||||
last_font = Some(&style.font);
|
last_font = Some(&style.font);
|
||||||
dbg!("f");
|
|
||||||
font_runs.push((*len, self.font_id(&style.font)?));
|
font_runs.push((*len, self.font_id(&style.font)?));
|
||||||
dbg!("g");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg!("built font runs");
|
|
||||||
|
|
||||||
let layout = self
|
let layout = self
|
||||||
.text_layout_cache
|
.text_layout_cache
|
||||||
.layout_line(text, font_size, &font_runs);
|
.layout_line(text, font_size, &font_runs);
|
||||||
|
@ -220,6 +200,17 @@ impl TextSystem {
|
||||||
text_system: self.clone(),
|
text_system: self.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||||
|
self.platform_text_system.glyph_raster_bounds(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rasterize_glyph(
|
||||||
|
&self,
|
||||||
|
glyph_id: &RenderGlyphParams,
|
||||||
|
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||||
|
self.platform_text_system.rasterize_glyph(glyph_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Hash, Eq, PartialEq)]
|
#[derive(Hash, Eq, PartialEq)]
|
||||||
|
@ -333,6 +324,7 @@ pub struct RunStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct GlyphId(u32);
|
pub struct GlyphId(u32);
|
||||||
|
|
||||||
impl From<GlyphId> for u32 {
|
impl From<GlyphId> for u32 {
|
||||||
|
@ -353,28 +345,69 @@ impl From<u32> for GlyphId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ShapedLine {
|
||||||
|
pub font_size: Pixels,
|
||||||
|
pub width: Pixels,
|
||||||
|
pub ascent: Pixels,
|
||||||
|
pub descent: Pixels,
|
||||||
|
pub runs: Vec<ShapedRun>,
|
||||||
|
pub len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShapedRun {
|
||||||
|
pub font_id: FontId,
|
||||||
|
pub glyphs: Vec<ShapedGlyph>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Glyph {
|
pub struct ShapedGlyph {
|
||||||
pub id: GlyphId,
|
pub id: GlyphId,
|
||||||
pub position: Point<Pixels>,
|
pub position: Point<Pixels>,
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub is_emoji: bool,
|
pub is_emoji: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct LineLayout {
|
pub struct RenderGlyphParams {
|
||||||
pub font_size: Pixels,
|
pub(crate) font_id: FontId,
|
||||||
pub width: Pixels,
|
pub(crate) glyph_id: GlyphId,
|
||||||
pub ascent: Pixels,
|
pub(crate) font_size: Pixels,
|
||||||
pub descent: Pixels,
|
pub(crate) subpixel_variant: Point<u8>,
|
||||||
pub runs: Vec<Run>,
|
pub(crate) scale_factor: f32,
|
||||||
pub len: usize,
|
pub(crate) is_emoji: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Eq for RenderGlyphParams {}
|
||||||
pub struct Run {
|
|
||||||
pub font_id: FontId,
|
impl Hash for RenderGlyphParams {
|
||||||
pub glyphs: Vec<Glyph>,
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.font_id.0.hash(state);
|
||||||
|
self.glyph_id.0.hash(state);
|
||||||
|
self.font_size.0.to_bits().hash(state);
|
||||||
|
self.subpixel_variant.hash(state);
|
||||||
|
self.scale_factor.to_bits().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct RenderEmojiParams {
|
||||||
|
pub(crate) font_id: FontId,
|
||||||
|
pub(crate) glyph_id: GlyphId,
|
||||||
|
pub(crate) font_size: Pixels,
|
||||||
|
pub(crate) scale_factor: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for RenderEmojiParams {}
|
||||||
|
|
||||||
|
impl Hash for RenderEmojiParams {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.font_id.0.hash(state);
|
||||||
|
self.glyph_id.0.hash(state);
|
||||||
|
self.font_size.0.to_bits().hash(state);
|
||||||
|
self.scale_factor.to_bits().hash(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
|
322
crates/gpui3/src/text_system/line.rs
Normal file
322
crates/gpui3/src/text_system/line.rs
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
use crate::{
|
||||||
|
black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
|
||||||
|
ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Line {
|
||||||
|
layout: Arc<ShapedLine>,
|
||||||
|
style_runs: SmallVec<[StyleRun; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct StyleRun {
|
||||||
|
len: u32,
|
||||||
|
color: Hsla,
|
||||||
|
underline: UnderlineStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Line {
|
||||||
|
pub fn new(layout: Arc<ShapedLine>, 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) -> &[ShapedRun] {
|
||||||
|
&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,
|
||||||
|
layout: &Layout,
|
||||||
|
visible_bounds: Bounds<Pixels>,
|
||||||
|
line_height: Pixels,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let origin = layout.bounds.origin;
|
||||||
|
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;
|
||||||
|
let text_system = cx.text_system().clone();
|
||||||
|
|
||||||
|
for run in &self.layout.runs {
|
||||||
|
let max_glyph_width = text_system
|
||||||
|
.bounding_box(run.font_id, 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 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
if glyph.is_emoji {
|
||||||
|
cx.paint_emoji(
|
||||||
|
glyph_origin,
|
||||||
|
layout.order,
|
||||||
|
run.font_id,
|
||||||
|
glyph.id,
|
||||||
|
self.layout.font_size,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
cx.paint_glyph(
|
||||||
|
glyph_origin,
|
||||||
|
layout.order,
|
||||||
|
run.font_id,
|
||||||
|
glyph.id,
|
||||||
|
self.layout.font_size,
|
||||||
|
color,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_system = cx.text_system();
|
||||||
|
let _glyph_bounds = Bounds {
|
||||||
|
origin: glyph_origin,
|
||||||
|
size: text_system
|
||||||
|
.bounding_box(run.font_id, 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,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,4 @@
|
||||||
use crate::{
|
use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun};
|
||||||
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 parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -13,8 +9,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct TextLayoutCache {
|
pub(crate) struct TextLayoutCache {
|
||||||
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
|
||||||
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
|
||||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +35,7 @@ impl TextLayoutCache {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
runs: &[(usize, FontId)],
|
runs: &[(usize, FontId)],
|
||||||
) -> Arc<LineLayout> {
|
) -> Arc<ShapedLine> {
|
||||||
dbg!("layout line");
|
|
||||||
let key = &CacheKeyRef {
|
let key = &CacheKeyRef {
|
||||||
text,
|
text,
|
||||||
font_size,
|
font_size,
|
||||||
|
@ -145,334 +140,14 @@ impl<'a> Hash for CacheKeyRef<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct ShapedBoundary {
|
pub struct ShapedBoundary {
|
||||||
pub run_ix: usize,
|
pub run_ix: usize,
|
||||||
pub glyph_ix: usize,
|
pub glyph_ix: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl ShapedRun {
|
||||||
pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
|
pub fn glyphs(&self) -> &[ShapedGlyph] {
|
||||||
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
|
&self.glyphs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,6 @@ use smol::future::FutureExt;
|
||||||
use std::{future::Future, time::Duration};
|
use std::{future::Future, time::Duration};
|
||||||
pub use util::*;
|
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, ()>
|
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
||||||
where
|
where
|
||||||
F: Future<Output = T>,
|
F: Future<Output = T>,
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
|
image_cache::RenderImageParams, px, AnyView, AppContext, AvailableSpace, BorrowAppContext,
|
||||||
LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
|
Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle,
|
||||||
StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
|
Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, MonochromeSprite,
|
||||||
|
Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, RenderGlyphParams,
|
||||||
|
RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, WeakHandle,
|
||||||
|
WindowOptions, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use std::{any::TypeId, marker::PhantomData, mem, sync::Arc};
|
use smallvec::SmallVec;
|
||||||
|
use std::{any::TypeId, borrow::Cow, marker::PhantomData, mem, sync::Arc};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub struct AnyWindow {}
|
pub struct AnyWindow {}
|
||||||
|
@ -13,11 +17,14 @@ pub struct AnyWindow {}
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
||||||
|
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||||
rem_size: Pixels,
|
rem_size: Pixels,
|
||||||
content_size: Size<Pixels>,
|
content_size: Size<Pixels>,
|
||||||
layout_engine: TaffyLayoutEngine,
|
layout_engine: TaffyLayoutEngine,
|
||||||
pub(crate) root_view: Option<AnyView<()>>,
|
pub(crate) root_view: Option<AnyView<()>>,
|
||||||
mouse_position: Point<Pixels>,
|
mouse_position: Point<Pixels>,
|
||||||
|
current_layer_id: LayerId,
|
||||||
|
content_mask_stack: Vec<ContentMask>,
|
||||||
pub(crate) scene: Scene,
|
pub(crate) scene: Scene,
|
||||||
pub(crate) dirty: bool,
|
pub(crate) dirty: bool,
|
||||||
}
|
}
|
||||||
|
@ -29,6 +36,7 @@ impl Window {
|
||||||
cx: &mut MainThread<AppContext>,
|
cx: &mut MainThread<AppContext>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let platform_window = cx.platform().open_window(handle, options);
|
let platform_window = cx.platform().open_window(handle, options);
|
||||||
|
let sprite_atlas = platform_window.sprite_atlas();
|
||||||
let mouse_position = platform_window.mouse_position();
|
let mouse_position = platform_window.mouse_position();
|
||||||
let content_size = platform_window.content_size();
|
let content_size = platform_window.content_size();
|
||||||
let scale_factor = platform_window.scale_factor();
|
let scale_factor = platform_window.scale_factor();
|
||||||
|
@ -51,17 +59,42 @@ impl Window {
|
||||||
Window {
|
Window {
|
||||||
handle,
|
handle,
|
||||||
platform_window,
|
platform_window,
|
||||||
|
sprite_atlas,
|
||||||
rem_size: px(16.),
|
rem_size: px(16.),
|
||||||
content_size,
|
content_size,
|
||||||
layout_engine: TaffyLayoutEngine::new(),
|
layout_engine: TaffyLayoutEngine::new(),
|
||||||
root_view: None,
|
root_view: None,
|
||||||
mouse_position,
|
mouse_position,
|
||||||
|
current_layer_id: SmallVec::new(),
|
||||||
|
content_mask_stack: Vec::new(),
|
||||||
scene: Scene::new(scale_factor),
|
scene: Scene::new(scale_factor),
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ContentMask {
|
||||||
|
pub bounds: Bounds<Pixels>,
|
||||||
|
pub corner_radii: Corners<Pixels>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentMask {
|
||||||
|
pub fn scale(&self, factor: f32) -> ScaledContentMask {
|
||||||
|
ScaledContentMask {
|
||||||
|
bounds: self.bounds.scale(factor),
|
||||||
|
corner_radii: self.corner_radii.scale(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ScaledContentMask {
|
||||||
|
bounds: Bounds<ScaledPixels>,
|
||||||
|
corner_radii: Corners<ScaledPixels>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WindowContext<'a, 'w> {
|
pub struct WindowContext<'a, 'w> {
|
||||||
app: Reference<'a, AppContext>,
|
app: Reference<'a, AppContext>,
|
||||||
window: Reference<'w, Window>,
|
window: Reference<'w, Window>,
|
||||||
|
@ -114,6 +147,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
.map(Into::into)?)
|
.map(Into::into)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scale_factor(&self) -> f32 {
|
||||||
|
self.window.scene.scale_factor
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rem_size(&self) -> Pixels {
|
pub fn rem_size(&self) -> Pixels {
|
||||||
self.window.rem_size
|
self.window.rem_size
|
||||||
}
|
}
|
||||||
|
@ -126,6 +163,34 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
&mut self.window.scene
|
&mut self.window.scene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.window.current_layer_id.push(order);
|
||||||
|
let result = f(self);
|
||||||
|
self.window.current_layer_id.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clip<F, R>(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
corner_radii: Corners<Pixels>,
|
||||||
|
f: impl FnOnce(&mut Self) -> R,
|
||||||
|
) -> R {
|
||||||
|
let clip_mask = ContentMask {
|
||||||
|
bounds,
|
||||||
|
corner_radii,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.window.content_mask_stack.push(clip_mask);
|
||||||
|
let result = f(self);
|
||||||
|
self.window.content_mask_stack.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_layer_id(&self) -> LayerId {
|
||||||
|
self.window.current_layer_id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_on_main<R>(
|
pub fn run_on_main<R>(
|
||||||
&self,
|
&self,
|
||||||
f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,
|
f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,
|
||||||
|
@ -143,6 +208,185 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paint_glyph(
|
||||||
|
&mut self,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
order: u32,
|
||||||
|
font_id: FontId,
|
||||||
|
glyph_id: GlyphId,
|
||||||
|
font_size: Pixels,
|
||||||
|
color: Hsla,
|
||||||
|
) -> Result<()> {
|
||||||
|
let scale_factor = self.scale_factor();
|
||||||
|
let glyph_origin = origin.scale(scale_factor);
|
||||||
|
let subpixel_variant = Point {
|
||||||
|
x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
||||||
|
y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
||||||
|
};
|
||||||
|
let params = RenderGlyphParams {
|
||||||
|
font_id,
|
||||||
|
glyph_id,
|
||||||
|
font_size,
|
||||||
|
subpixel_variant,
|
||||||
|
scale_factor,
|
||||||
|
is_emoji: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||||
|
if !raster_bounds.is_zero() {
|
||||||
|
let layer_id = self.current_layer_id();
|
||||||
|
let tile =
|
||||||
|
self.window
|
||||||
|
.sprite_atlas
|
||||||
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
|
let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?;
|
||||||
|
Ok((size, Cow::Owned(bytes)))
|
||||||
|
})?;
|
||||||
|
let bounds = Bounds {
|
||||||
|
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
|
||||||
|
size: tile.bounds.size.map(Into::into),
|
||||||
|
};
|
||||||
|
let content_mask = self.content_mask().scale(scale_factor);
|
||||||
|
|
||||||
|
self.window.scene.insert(
|
||||||
|
layer_id,
|
||||||
|
MonochromeSprite {
|
||||||
|
order,
|
||||||
|
bounds,
|
||||||
|
content_mask,
|
||||||
|
color,
|
||||||
|
tile,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint_emoji(
|
||||||
|
&mut self,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
order: u32,
|
||||||
|
font_id: FontId,
|
||||||
|
glyph_id: GlyphId,
|
||||||
|
font_size: Pixels,
|
||||||
|
) -> Result<()> {
|
||||||
|
let scale_factor = self.scale_factor();
|
||||||
|
let glyph_origin = origin.scale(scale_factor);
|
||||||
|
let params = RenderGlyphParams {
|
||||||
|
font_id,
|
||||||
|
glyph_id,
|
||||||
|
font_size,
|
||||||
|
// We don't render emojis with subpixel variants.
|
||||||
|
subpixel_variant: Default::default(),
|
||||||
|
scale_factor,
|
||||||
|
is_emoji: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||||
|
if !raster_bounds.is_zero() {
|
||||||
|
let layer_id = self.current_layer_id();
|
||||||
|
let tile =
|
||||||
|
self.window
|
||||||
|
.sprite_atlas
|
||||||
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
|
let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?;
|
||||||
|
Ok((size, Cow::Owned(bytes)))
|
||||||
|
})?;
|
||||||
|
let bounds = Bounds {
|
||||||
|
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
|
||||||
|
size: tile.bounds.size.map(Into::into),
|
||||||
|
};
|
||||||
|
let content_mask = self.content_mask().scale(scale_factor);
|
||||||
|
|
||||||
|
self.window.scene.insert(
|
||||||
|
layer_id,
|
||||||
|
PolychromeSprite {
|
||||||
|
order,
|
||||||
|
bounds,
|
||||||
|
content_mask,
|
||||||
|
tile,
|
||||||
|
grayscale: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint_svg(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
order: u32,
|
||||||
|
path: SharedString,
|
||||||
|
color: Hsla,
|
||||||
|
) -> Result<()> {
|
||||||
|
let scale_factor = self.scale_factor();
|
||||||
|
let bounds = bounds.scale(scale_factor);
|
||||||
|
// Render the SVG at twice the size to get a higher quality result.
|
||||||
|
let params = RenderSvgParams {
|
||||||
|
path,
|
||||||
|
size: bounds
|
||||||
|
.size
|
||||||
|
.map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer_id = self.current_layer_id();
|
||||||
|
let tile =
|
||||||
|
self.window
|
||||||
|
.sprite_atlas
|
||||||
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
|
let bytes = self.svg_renderer.render(¶ms)?;
|
||||||
|
Ok((params.size, Cow::Owned(bytes)))
|
||||||
|
})?;
|
||||||
|
let content_mask = self.content_mask().scale(scale_factor);
|
||||||
|
|
||||||
|
self.window.scene.insert(
|
||||||
|
layer_id,
|
||||||
|
MonochromeSprite {
|
||||||
|
order,
|
||||||
|
bounds,
|
||||||
|
content_mask,
|
||||||
|
color,
|
||||||
|
tile,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint_image(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
order: u32,
|
||||||
|
data: Arc<ImageData>,
|
||||||
|
grayscale: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let scale_factor = self.scale_factor();
|
||||||
|
let bounds = bounds.scale(scale_factor);
|
||||||
|
let params = RenderImageParams { image_id: data.id };
|
||||||
|
|
||||||
|
let layer_id = self.current_layer_id();
|
||||||
|
let tile = self
|
||||||
|
.window
|
||||||
|
.sprite_atlas
|
||||||
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
|
Ok((data.size(), Cow::Borrowed(data.as_bytes())))
|
||||||
|
})?;
|
||||||
|
let content_mask = self.content_mask().scale(scale_factor);
|
||||||
|
|
||||||
|
self.window.scene.insert(
|
||||||
|
layer_id,
|
||||||
|
PolychromeSprite {
|
||||||
|
order,
|
||||||
|
bounds,
|
||||||
|
content_mask,
|
||||||
|
tile,
|
||||||
|
grayscale,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn draw(&mut self) -> Result<()> {
|
pub(crate) fn draw(&mut self) -> Result<()> {
|
||||||
let unit_entity = self.unit_entity.clone();
|
let unit_entity = self.unit_entity.clone();
|
||||||
self.update_entity(&unit_entity, |_, cx| {
|
self.update_entity(&unit_entity, |_, cx| {
|
||||||
|
@ -150,15 +394,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
|
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
|
||||||
let available_space = cx.window.content_size.map(Into::into);
|
let available_space = cx.window.content_size.map(Into::into);
|
||||||
|
|
||||||
dbg!("computing layout");
|
|
||||||
cx.window
|
cx.window
|
||||||
.layout_engine
|
.layout_engine
|
||||||
.compute_layout(root_layout_id, available_space)?;
|
.compute_layout(root_layout_id, available_space)?;
|
||||||
dbg!("asking for layout");
|
|
||||||
let layout = cx.window.layout_engine.layout(root_layout_id)?;
|
let layout = cx.window.layout_engine.layout(root_layout_id)?;
|
||||||
|
|
||||||
dbg!("painting root view");
|
|
||||||
|
|
||||||
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
|
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
|
||||||
cx.window.root_view = Some(root_view);
|
cx.window.root_view = Some(root_view);
|
||||||
let scene = cx.window.scene.take();
|
let scene = cx.window.scene.take();
|
||||||
|
@ -176,13 +416,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<'_, '_> {
|
impl Context for WindowContext<'_, '_> {
|
||||||
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
|
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
|
||||||
type Result<T> = T;
|
type Result<T> = T;
|
||||||
|
@ -229,8 +462,60 @@ impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> StackContext for ViewContext<'_, '_, S> {
|
impl BorrowAppContext for WindowContext<'_, '_> {
|
||||||
fn app(&mut self) -> &mut AppContext {
|
fn app_mut(&mut self) -> &mut AppContext {
|
||||||
|
&mut *self.app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BorrowWindow: BorrowAppContext {
|
||||||
|
fn window(&self) -> &Window;
|
||||||
|
fn window_mut(&mut self) -> &mut Window;
|
||||||
|
|
||||||
|
fn with_content_mask<R>(&mut self, mask: ContentMask, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.window_mut().content_mask_stack.push(mask);
|
||||||
|
let result = f(self);
|
||||||
|
self.window_mut().content_mask_stack.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn content_mask(&self) -> ContentMask {
|
||||||
|
self.window()
|
||||||
|
.content_mask_stack
|
||||||
|
.last()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| ContentMask {
|
||||||
|
bounds: Bounds {
|
||||||
|
origin: Point::default(),
|
||||||
|
size: self.window().content_size,
|
||||||
|
},
|
||||||
|
corner_radii: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rem_size(&self) -> Pixels {
|
||||||
|
self.window().rem_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorrowWindow for WindowContext<'_, '_> {
|
||||||
|
fn window(&self) -> &Window {
|
||||||
|
&*self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_mut(&mut self) -> &mut Window {
|
||||||
|
&mut *self.window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ViewContext<'a, 'w, S> {
|
||||||
|
window_cx: WindowContext<'a, 'w>,
|
||||||
|
entity_type: PhantomData<S>,
|
||||||
|
entity_id: EntityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> BorrowAppContext for ViewContext<'_, '_, S> {
|
||||||
|
fn app_mut(&mut self) -> &mut AppContext {
|
||||||
&mut *self.window_cx.app
|
&mut *self.window_cx.app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,10 +540,14 @@ impl<S> StackContext for ViewContext<'_, '_, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ViewContext<'a, 'w, S> {
|
impl<S> BorrowWindow for ViewContext<'_, '_, S> {
|
||||||
window_cx: WindowContext<'a, 'w>,
|
fn window(&self) -> &Window {
|
||||||
entity_type: PhantomData<S>,
|
&self.window_cx.window
|
||||||
entity_id: EntityId,
|
}
|
||||||
|
|
||||||
|
fn window_mut(&mut self) -> &mut Window {
|
||||||
|
&mut *self.window_cx.window
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
|
impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
|
||||||
|
|
30
crates/storybook2/src/assets.rs
Normal file
30
crates/storybook2/src/assets.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use gpui3::{AssetSource, SharedString};
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "../../assets"]
|
||||||
|
#[include = "fonts/**/*"]
|
||||||
|
#[include = "icons/**/*"]
|
||||||
|
#[include = "themes/**/*"]
|
||||||
|
#[include = "sounds/**/*"]
|
||||||
|
#[include = "*.md"]
|
||||||
|
#[exclude = "*.DS_Store"]
|
||||||
|
pub struct Assets;
|
||||||
|
|
||||||
|
impl AssetSource for Assets {
|
||||||
|
fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
|
||||||
|
Self::get(path.as_ref())
|
||||||
|
.map(|f| f.data)
|
||||||
|
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&self, path: &SharedString) -> Result<Vec<SharedString>> {
|
||||||
|
Ok(Self::iter()
|
||||||
|
.filter(|p| p.starts_with(path.as_ref()))
|
||||||
|
.map(SharedString::from)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::theme::{theme, Theme};
|
use crate::theme::{theme, Theme};
|
||||||
use gpui3::{
|
use gpui3::{
|
||||||
div, img, svg, view, AppContext, ArcCow, Context, Element, IntoAnyElement, ParentElement,
|
div, img, svg, view, AppContext, Context, Element, IntoAnyElement, ParentElement, ScrollState,
|
||||||
ScrollState, StyleHelpers, View, ViewContext, WindowContext,
|
SharedString, StyleHelpers, View, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CollabPanel {
|
pub struct CollabPanel {
|
||||||
|
@ -30,7 +30,7 @@ impl CollabPanel {
|
||||||
.h_full()
|
.h_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.font("Zed Sans Extended")
|
.font("Courier")
|
||||||
.text_color(theme.middle.base.default.foreground)
|
.text_color(theme.middle.base.default.foreground)
|
||||||
.border_color(theme.middle.base.default.border)
|
.border_color(theme.middle.base.default.border)
|
||||||
.border()
|
.border()
|
||||||
|
@ -51,7 +51,7 @@ impl CollabPanel {
|
||||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
||||||
// .group()
|
// .group()
|
||||||
// List Section Header
|
// List Section Header
|
||||||
.child(self.list_section_header("#CRDB", true, theme))
|
.child(self.list_section_header("#CRDB 🗃️", true, theme))
|
||||||
// List Item Large
|
// List Item Large
|
||||||
.child(self.list_item(
|
.child(self.list_item(
|
||||||
"http://github.com/maxbrunsfeld.png?s=50",
|
"http://github.com/maxbrunsfeld.png?s=50",
|
||||||
|
@ -144,7 +144,7 @@ impl CollabPanel {
|
||||||
|
|
||||||
fn list_item(
|
fn list_item(
|
||||||
&self,
|
&self,
|
||||||
avatar_uri: impl Into<ArcCow<'static, str>>,
|
avatar_uri: impl Into<SharedString>,
|
||||||
label: impl IntoAnyElement<Self>,
|
label: impl IntoAnyElement<Self>,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
) -> impl Element<State = Self> {
|
) -> impl Element<State = Self> {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
use gpui3::{Bounds, WindowBounds, WindowOptions};
|
use assets::Assets;
|
||||||
|
use gpui3::{px, size, Bounds, WindowBounds, WindowOptions};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use workspace::workspace;
|
||||||
|
|
||||||
|
mod assets;
|
||||||
mod collab_panel;
|
mod collab_panel;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod themes;
|
mod themes;
|
||||||
|
@ -20,15 +24,13 @@ fn main() {
|
||||||
|
|
||||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||||
|
|
||||||
gpui3::App::production().run(|cx| {
|
let asset_source = Arc::new(Assets);
|
||||||
|
gpui3::App::production(asset_source).run(|cx| {
|
||||||
let window = cx.open_window(
|
let window = cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: WindowBounds::Fixed(Bounds {
|
bounds: WindowBounds::Fixed(Bounds {
|
||||||
size: gpui3::Size {
|
origin: Default::default(),
|
||||||
width: 800_f32.into(),
|
size: size(px(800.), px(600.)),
|
||||||
height: 600_f32.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -39,29 +41,6 @@ fn main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
|
||||||
// let font_paths = Assets.list("fonts");
|
// let font_paths = Assets.list("fonts");
|
||||||
// let mut embedded_fonts = Vec::new();
|
// let mut embedded_fonts = Vec::new();
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use gpui3::{Element, Hsla, Layout, LayoutId, Result, StackContext, ViewContext, WindowContext};
|
use gpui3::{
|
||||||
|
BorrowAppContext, Element, Hsla, Layout, LayoutId, Result, ViewContext, WindowContext,
|
||||||
|
};
|
||||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::ui::prelude::*;
|
||||||
use crate::ui::{Panel, Stack};
|
use crate::ui::{Panel, Stack};
|
||||||
use crate::{
|
use crate::{
|
||||||
collab_panel::{collab_panel, CollabPanel},
|
collab_panel::{collab_panel, CollabPanel},
|
||||||
theme::theme,
|
theme::{theme, themed},
|
||||||
themes::rose_pine_dawn,
|
themes::rose_pine_dawn,
|
||||||
};
|
};
|
||||||
use gpui3::{
|
use gpui3::{
|
||||||
|
@ -56,31 +56,17 @@ impl Workspace {
|
||||||
.fill(theme.middle.negative.default.background),
|
.fill(theme.middle.negative.default.background),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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| {
|
// themed(rose_pine_dawn(), cx, |cx| {
|
||||||
// div()
|
// div()
|
||||||
// .size_full()
|
// .size_full()
|
||||||
// .flex()
|
// .flex()
|
||||||
// .flex_col()
|
// .flex_col()
|
||||||
// .font("Zed Sans Extended")
|
// .font("Courier")
|
||||||
// .gap_0()
|
// .gap_0()
|
||||||
// .justify_start()
|
// .justify_start()
|
||||||
// .items_start()
|
// .items_start()
|
||||||
// .text_color(theme.lowest.base.default.foreground)
|
// .text_color(theme.lowest.base.default.foreground)
|
||||||
// // .fill(theme.middle.base.default.background)
|
// .fill(theme.middle.base.default.background)
|
||||||
// .fill(gpui3::hsla(0.83, 1., 0.5, 1.))
|
|
||||||
// .child(titlebar(cx))
|
// .child(titlebar(cx))
|
||||||
// .child(
|
// .child(
|
||||||
// div()
|
// div()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
@ -47,6 +48,15 @@ impl From<String> for ArcCow<'_, str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Cow<'a, str>> for ArcCow<'a, str> {
|
||||||
|
fn from(value: Cow<'a, str>) -> Self {
|
||||||
|
match value {
|
||||||
|
Cow::Borrowed(borrowed) => Self::Borrowed(borrowed),
|
||||||
|
Cow::Owned(owned) => Self::Owned(owned.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T: ?Sized + ToOwned> std::borrow::Borrow<T> for ArcCow<'a, T> {
|
impl<'a, T: ?Sized + ToOwned> std::borrow::Borrow<T> for ArcCow<'a, T> {
|
||||||
fn borrow(&self) -> &T {
|
fn borrow(&self) -> &T {
|
||||||
match self {
|
match self {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue