Checkpoint
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "gpui3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
test = []
|
||||
|
||||
[lib]
|
||||
path = "src/gpui3.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
bytemuck = "1.14.0"
|
||||
derive_more.workspace = true
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
|
||||
itertools = "0.11.0"
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
plane-split = "0.18.0"
|
||||
raw-window-handle = "0.5.2"
|
||||
refineable = { path = "../refineable" }
|
||||
rust-embed.workspace = true
|
||||
schemars = "0.8"
|
||||
serde.workspace = true
|
||||
simplelog = "0.9"
|
||||
slotmap = "1.0.6"
|
||||
smallvec.workspace = true
|
||||
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
|
||||
util = { path = "../util" }
|
||||
wgpu = "0.17.0"
|
||||
@@ -0,0 +1,217 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use slotmap::SlotMap;
|
||||
use std::{any::Any, marker::PhantomData, rc::Rc, sync::Arc};
|
||||
|
||||
use crate::FontCache;
|
||||
|
||||
use super::{
|
||||
platform::Platform,
|
||||
window::{Window, WindowHandle, WindowId},
|
||||
Context, LayoutId, Reference, View, WindowContext,
|
||||
};
|
||||
|
||||
pub struct AppContext {
|
||||
platform: Rc<dyn Platform>,
|
||||
font_cache: Arc<FontCache>,
|
||||
pub(crate) entities: SlotMap<EntityId, Option<Box<dyn Any>>>,
|
||||
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
|
||||
// We recycle this memory across layout requests.
|
||||
pub(crate) layout_id_buffer: Vec<LayoutId>,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
pub fn new(platform: Rc<dyn Platform>) -> Self {
|
||||
let font_cache = Arc::new(FontCache::new(platform.font_system()));
|
||||
AppContext {
|
||||
platform,
|
||||
font_cache,
|
||||
entities: SlotMap::with_key(),
|
||||
windows: SlotMap::with_key(),
|
||||
layout_id_buffer: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub fn test() -> Self {
|
||||
Self::new(Rc::new(super::TestPlatform::new()))
|
||||
}
|
||||
|
||||
pub fn platform(&self) -> &Rc<dyn Platform> {
|
||||
&self.platform
|
||||
}
|
||||
|
||||
pub fn font_cache(&self) -> &Arc<FontCache> {
|
||||
&self.font_cache
|
||||
}
|
||||
|
||||
pub fn open_window<S: 'static>(
|
||||
&mut self,
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> View<S>,
|
||||
) -> WindowHandle<S> {
|
||||
let id = self.windows.insert(None);
|
||||
let handle = WindowHandle::new(id);
|
||||
self.platform.open_window(handle.into(), options);
|
||||
|
||||
let mut window = Window::new(id);
|
||||
let root_view = build_root_view(&mut WindowContext::mutable(self, &mut window));
|
||||
window.root_view.replace(Box::new(root_view));
|
||||
|
||||
self.windows.get_mut(id).unwrap().replace(window);
|
||||
handle
|
||||
}
|
||||
|
||||
pub(crate) fn update_window<R>(
|
||||
&mut self,
|
||||
window_id: WindowId,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
let mut window = self
|
||||
.windows
|
||||
.get_mut(window_id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.take()
|
||||
.unwrap();
|
||||
|
||||
let result = update(&mut WindowContext::mutable(self, &mut window));
|
||||
|
||||
self.windows
|
||||
.get_mut(window_id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.replace(window);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
type EntityContext<'a, 'w, T: 'static> = ModelContext<'a, T>;
|
||||
|
||||
fn entity<T: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Handle<T> {
|
||||
let id = self.entities.insert(None);
|
||||
let entity = Box::new(build_entity(&mut ModelContext::mutable(self, id)));
|
||||
self.entities.get_mut(id).unwrap().replace(entity);
|
||||
|
||||
Handle::new(id)
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> R {
|
||||
let mut entity = self
|
||||
.entities
|
||||
.get_mut(handle.id)
|
||||
.unwrap()
|
||||
.take()
|
||||
.unwrap()
|
||||
.downcast::<T>()
|
||||
.unwrap();
|
||||
|
||||
let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id));
|
||||
self.entities.get_mut(handle.id).unwrap().replace(entity);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelContext<'a, T> {
|
||||
app: Reference<'a, AppContext>,
|
||||
entity_type: PhantomData<T>,
|
||||
entity_id: EntityId,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self {
|
||||
Self {
|
||||
app: Reference::Mutable(app),
|
||||
entity_type: PhantomData,
|
||||
entity_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn immutable(app: &'a AppContext, entity_id: EntityId) -> Self {
|
||||
Self {
|
||||
app: Reference::Immutable(app),
|
||||
entity_type: PhantomData,
|
||||
entity_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
|
||||
let mut entity = self
|
||||
.app
|
||||
.entities
|
||||
.get_mut(self.entity_id)
|
||||
.unwrap()
|
||||
.take()
|
||||
.unwrap();
|
||||
let result = update(entity.downcast_mut::<T>().unwrap(), self);
|
||||
self.app
|
||||
.entities
|
||||
.get_mut(self.entity_id)
|
||||
.unwrap()
|
||||
.replace(entity);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Context for ModelContext<'a, T> {
|
||||
type EntityContext<'b, 'c, U: 'static> = ModelContext<'b, U>;
|
||||
|
||||
fn entity<U: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U,
|
||||
) -> Handle<U> {
|
||||
self.app.entity(build_entity)
|
||||
}
|
||||
|
||||
fn update_entity<U: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<U>,
|
||||
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
|
||||
) -> R {
|
||||
self.app.update_entity(handle, update)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Handle<T> {
|
||||
pub(crate) id: EntityId,
|
||||
pub(crate) entity_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
slotmap::new_key_type! { pub struct EntityId; }
|
||||
|
||||
impl<T: 'static> Handle<T> {
|
||||
fn new(id: EntityId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the entity referenced by this handle with the given function.
|
||||
///
|
||||
/// The update function receives a context appropriate for its environment.
|
||||
/// When updating in an `AppContext`, it receives a `ModelContext`.
|
||||
/// When updating an a `WindowContext`, it receives a `ViewContext`.
|
||||
pub fn update<C: Context, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
|
||||
) -> R {
|
||||
cx.update_entity(self, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||
use std::fmt;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
|
||||
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
||||
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
|
||||
let b = (hex & 0xFF) as f32 / 255.0;
|
||||
Rgba { r, g, b, a: 1.0 }.into()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct Rgba {
|
||||
pub r: f32,
|
||||
pub g: f32,
|
||||
pub b: f32,
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub fn blend(&self, other: Rgba) -> Self {
|
||||
if other.a >= 1.0 {
|
||||
return other;
|
||||
} else if other.a <= 0.0 {
|
||||
return *self;
|
||||
} else {
|
||||
return Rgba {
|
||||
r: (self.r * (1.0 - other.a)) + (other.r * other.a),
|
||||
g: (self.g * (1.0 - other.a)) + (other.g * other.a),
|
||||
b: (self.b * (1.0 - other.a)) + (other.b * other.a),
|
||||
a: self.a,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RgbaVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RgbaVisitor {
|
||||
type Value = Rgba;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
|
||||
if value.len() == 7 || value.len() == 9 {
|
||||
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
|
||||
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
|
||||
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
|
||||
let a = if value.len() == 9 {
|
||||
u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
Ok(Rgba { r, g, b, a })
|
||||
} else {
|
||||
Err(E::custom(
|
||||
"Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Rgba {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_str(RgbaVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for Rgba {
|
||||
fn from(color: Hsla) -> Self {
|
||||
let h = color.h;
|
||||
let s = color.s;
|
||||
let l = color.l;
|
||||
|
||||
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
|
||||
let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
|
||||
let m = l - c / 2.0;
|
||||
let cm = c + m;
|
||||
let xm = x + m;
|
||||
|
||||
let (r, g, b) = match (h * 6.0).floor() as i32 {
|
||||
0 | 6 => (cm, xm, m),
|
||||
1 => (xm, cm, m),
|
||||
2 => (m, cm, xm),
|
||||
3 => (m, xm, cm),
|
||||
4 => (xm, m, cm),
|
||||
_ => (cm, m, xm),
|
||||
};
|
||||
|
||||
Rgba {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for Rgba {
|
||||
type Error = ParseIntError;
|
||||
|
||||
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
|
||||
let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
|
||||
let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
|
||||
let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
|
||||
let a = if value.len() > 7 {
|
||||
u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
Ok(Rgba { r, g, b, a })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Hsla {
|
||||
pub h: f32,
|
||||
pub s: f32,
|
||||
pub l: f32,
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Eq for Hsla {}
|
||||
unsafe impl Zeroable for Hsla {}
|
||||
unsafe impl Pod for Hsla {}
|
||||
|
||||
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
||||
Hsla {
|
||||
h: h.clamp(0., 1.),
|
||||
s: s.clamp(0., 1.),
|
||||
l: l.clamp(0., 1.),
|
||||
a: a.clamp(0., 1.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn black() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
s: 0.,
|
||||
l: 0.,
|
||||
a: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
impl Hsla {
|
||||
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
self.a == 0.0
|
||||
}
|
||||
|
||||
/// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
|
||||
///
|
||||
/// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
|
||||
/// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
|
||||
/// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
|
||||
///
|
||||
/// Assumptions:
|
||||
/// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
|
||||
/// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
|
||||
/// - RGB color components are contained in the range [0, 1].
|
||||
/// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
|
||||
pub fn blend(self, other: Hsla) -> Hsla {
|
||||
let alpha = other.a;
|
||||
|
||||
if alpha >= 1.0 {
|
||||
return other;
|
||||
} else if alpha <= 0.0 {
|
||||
return self;
|
||||
} else {
|
||||
let converted_self = Rgba::from(self);
|
||||
let converted_other = Rgba::from(other);
|
||||
let blended_rgb = converted_self.blend(converted_other);
|
||||
return Hsla::from(blended_rgb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
|
||||
/// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
|
||||
pub fn fade_out(&mut self, factor: f32) {
|
||||
self.a *= 1.0 - factor.clamp(0., 1.);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for Hsla {
|
||||
fn from(color: Rgba) -> Self {
|
||||
let r = color.r;
|
||||
let g = color.g;
|
||||
let b = color.b;
|
||||
|
||||
let max = r.max(g.max(b));
|
||||
let min = r.min(g.min(b));
|
||||
let delta = max - min;
|
||||
|
||||
let l = (max + min) / 2.0;
|
||||
let s = if l == 0.0 || l == 1.0 {
|
||||
0.0
|
||||
} else if l < 0.5 {
|
||||
delta / (2.0 * l)
|
||||
} else {
|
||||
delta / (2.0 - 2.0 * l)
|
||||
};
|
||||
|
||||
let h = if delta == 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
((g - b) / delta).rem_euclid(6.0) / 6.0
|
||||
} else if max == g {
|
||||
((b - r) / delta + 2.0) / 6.0
|
||||
} else {
|
||||
((r - g) / delta + 4.0) / 6.0
|
||||
};
|
||||
|
||||
Hsla {
|
||||
h,
|
||||
s,
|
||||
l,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Hsla {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// First, deserialize it into Rgba
|
||||
let rgba = Rgba::deserialize(deserializer)?;
|
||||
|
||||
// Then, use the From<Rgba> for Hsla implementation to convert it
|
||||
Ok(Hsla::from(rgba))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
use super::{Handle, Layout, LayoutId, Pixels, Point, Result, ViewContext, WindowContext};
|
||||
use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
|
||||
|
||||
pub trait Element: 'static {
|
||||
type State;
|
||||
type FrameState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)>;
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
state: &mut Self::State,
|
||||
frame_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait ParentElement<S> {
|
||||
fn child(self, child: impl IntoAnyElement<S>) -> Self;
|
||||
}
|
||||
|
||||
trait ElementObject<S> {
|
||||
fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId>;
|
||||
fn paint(
|
||||
&mut self,
|
||||
parent_origin: super::Point<Pixels>,
|
||||
state: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
struct RenderedElement<E: Element> {
|
||||
element: E,
|
||||
phase: ElementRenderPhase<E::FrameState>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum ElementRenderPhase<S> {
|
||||
#[default]
|
||||
Rendered,
|
||||
LayoutRequested {
|
||||
layout_id: LayoutId,
|
||||
frame_state: S,
|
||||
},
|
||||
Painted {
|
||||
layout: Layout,
|
||||
frame_state: S,
|
||||
},
|
||||
}
|
||||
|
||||
/// Internal struct that wraps an element to store Layout and FrameState after the element is rendered.
|
||||
/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
|
||||
/// improved usability.
|
||||
impl<E: Element> RenderedElement<E> {
|
||||
fn new(element: E) -> Self {
|
||||
RenderedElement {
|
||||
element,
|
||||
phase: ElementRenderPhase::Rendered,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
|
||||
fn layout(&mut self, state: &mut E::State, cx: &mut ViewContext<E::State>) -> Result<LayoutId> {
|
||||
let (layout_id, frame_state) = self.element.layout(state, cx)?;
|
||||
self.phase = ElementRenderPhase::LayoutRequested {
|
||||
layout_id,
|
||||
frame_state,
|
||||
};
|
||||
Ok(layout_id)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
parent_origin: Point<Pixels>,
|
||||
state: &mut E::State,
|
||||
cx: &mut ViewContext<E::State>,
|
||||
) -> Result<()> {
|
||||
self.phase = match std::mem::take(&mut self.phase) {
|
||||
ElementRenderPhase::Rendered => panic!("must call layout before paint"),
|
||||
|
||||
ElementRenderPhase::LayoutRequested {
|
||||
layout_id,
|
||||
mut frame_state,
|
||||
} => {
|
||||
let mut layout = cx.layout(layout_id)?;
|
||||
layout.bounds.origin += parent_origin;
|
||||
self.element
|
||||
.paint(layout.clone(), state, &mut frame_state, cx)?;
|
||||
ElementRenderPhase::Painted {
|
||||
layout,
|
||||
frame_state,
|
||||
}
|
||||
}
|
||||
|
||||
ElementRenderPhase::Painted {
|
||||
layout,
|
||||
mut frame_state,
|
||||
} => {
|
||||
self.element
|
||||
.paint(layout.clone(), state, &mut frame_state, cx)?;
|
||||
ElementRenderPhase::Painted {
|
||||
layout,
|
||||
frame_state,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyElement<S>(Box<dyn ElementObject<S>>);
|
||||
|
||||
impl<S> AnyElement<S> {
|
||||
pub fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId> {
|
||||
self.0.layout(state, cx)
|
||||
}
|
||||
|
||||
pub fn paint(
|
||||
&mut self,
|
||||
parent_origin: Point<Pixels>,
|
||||
state: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()> {
|
||||
self.0.paint(parent_origin, state, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoAnyElement<S> {
|
||||
fn into_any(self) -> AnyElement<S>;
|
||||
}
|
||||
|
||||
impl<E: Element> IntoAnyElement<E::State> for E {
|
||||
fn into_any(self) -> AnyElement<E::State> {
|
||||
AnyElement(Box::new(RenderedElement::new(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoAnyElement<S> for AnyElement<S> {
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct View<S> {
|
||||
state: Handle<S>,
|
||||
render: Rc<dyn Fn(&mut S, &mut ViewContext<S>) -> AnyElement<S>>,
|
||||
}
|
||||
|
||||
pub fn view<S: 'static, E: Element<State = S>>(
|
||||
state: Handle<S>,
|
||||
render: impl 'static + Fn(&mut S, &mut ViewContext<S>) -> E,
|
||||
) -> View<S> {
|
||||
View {
|
||||
state,
|
||||
render: Rc::new(move |state, cx| render(state, cx).into_any()),
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> View<S> {
|
||||
pub fn into_any<ParentState>(self) -> AnyView<ParentState> {
|
||||
AnyView {
|
||||
view: Rc::new(RefCell::new(self)),
|
||||
parent_state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for View<S> {
|
||||
type State = ();
|
||||
type FrameState = AnyElement<S>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let mut element = (self.render)(state, cx);
|
||||
let layout_id = element.layout(state, cx)?;
|
||||
Ok((layout_id, element))
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
element: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
element.paint(layout.bounds.origin, state, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
trait ViewObject {
|
||||
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
element: &mut dyn Any,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<S: 'static> ViewObject for View<S> {
|
||||
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let mut element = (self.render)(state, cx);
|
||||
let layout_id = element.layout(state, cx)?;
|
||||
let element = Box::new(element) as Box<dyn Any>;
|
||||
Ok((layout_id, element))
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
element: &mut dyn Any,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
self.state.update(cx, |state, cx| {
|
||||
element
|
||||
.downcast_mut::<AnyElement<S>>()
|
||||
.unwrap()
|
||||
.paint(layout.bounds.origin, state, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyView<S> {
|
||||
view: Rc<RefCell<dyn ViewObject>>,
|
||||
parent_state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for AnyView<S> {
|
||||
type State = S;
|
||||
type FrameState = Box<dyn Any>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
self.view.borrow_mut().layout(cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
element: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
self.view.borrow_mut().paint(layout, element, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for AnyView<S> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
view: self.view.clone(),
|
||||
parent_state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod div;
|
||||
pub mod editor;
|
||||
|
||||
use super::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub use div::div;
|
||||
pub use editor::field;
|
||||
@@ -0,0 +1,38 @@
|
||||
use super::{
|
||||
Element, IntoAnyElement, Layout, LayoutId, ParentElement, PhantomData, Result, ViewContext,
|
||||
};
|
||||
|
||||
pub struct Div<S>(PhantomData<S>);
|
||||
|
||||
impl<S: 'static> Element for Div<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_state: &mut Self::State,
|
||||
_cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_layout: Layout,
|
||||
_state: &mut Self::State,
|
||||
_frame_state: &mut Self::FrameState,
|
||||
_cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ParentElement<S> for Div<S> {
|
||||
fn child(self, _child: impl IntoAnyElement<S>) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div<S>() -> Div<S> {
|
||||
todo!()
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use super::{Element, Handle, Layout, LayoutId, Result, SharedString, ViewContext};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub fn field<S>(editor: Handle<Editor>) -> EditorElement<S> {
|
||||
EditorElement {
|
||||
editor,
|
||||
field: true,
|
||||
placeholder_text: None,
|
||||
parent_state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorElement<S> {
|
||||
editor: Handle<Editor>,
|
||||
field: bool,
|
||||
placeholder_text: Option<SharedString>,
|
||||
parent_state: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> EditorElement<S> {
|
||||
pub fn field(mut self) -> Self {
|
||||
self.field = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn placeholder_text(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.placeholder_text = Some(text.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for EditorElement<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
self.editor.update(cx, |_editor, _cx| todo!())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_layout: Layout,
|
||||
_state: &mut Self::State,
|
||||
_frame_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
self.editor.update(cx, |_editor, _cx| todo!())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Editor {}
|
||||
|
||||
impl Editor {
|
||||
pub fn new(_: &mut ViewContext<Self>) -> Self {
|
||||
Editor {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
use crate::{px, Bounds, LineWrapper, Pixels, PlatformFontSystem, Result, Size};
|
||||
use anyhow::anyhow;
|
||||
pub use font_kit::properties::{
|
||||
Properties as FontProperties, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
|
||||
};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FontFamilyId(usize);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FontId(pub usize);
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FontFeatures {
|
||||
pub calt: Option<bool>,
|
||||
pub case: Option<bool>,
|
||||
pub cpsp: Option<bool>,
|
||||
pub frac: Option<bool>,
|
||||
pub liga: Option<bool>,
|
||||
pub onum: Option<bool>,
|
||||
pub ordn: Option<bool>,
|
||||
pub pnum: Option<bool>,
|
||||
pub ss01: Option<bool>,
|
||||
pub ss02: Option<bool>,
|
||||
pub ss03: Option<bool>,
|
||||
pub ss04: Option<bool>,
|
||||
pub ss05: Option<bool>,
|
||||
pub ss06: Option<bool>,
|
||||
pub ss07: Option<bool>,
|
||||
pub ss08: Option<bool>,
|
||||
pub ss09: Option<bool>,
|
||||
pub ss10: Option<bool>,
|
||||
pub ss11: Option<bool>,
|
||||
pub ss12: Option<bool>,
|
||||
pub ss13: Option<bool>,
|
||||
pub ss14: Option<bool>,
|
||||
pub ss15: Option<bool>,
|
||||
pub ss16: Option<bool>,
|
||||
pub ss17: Option<bool>,
|
||||
pub ss18: Option<bool>,
|
||||
pub ss19: Option<bool>,
|
||||
pub ss20: Option<bool>,
|
||||
pub subs: Option<bool>,
|
||||
pub sups: Option<bool>,
|
||||
pub swsh: Option<bool>,
|
||||
pub titl: Option<bool>,
|
||||
pub tnum: Option<bool>,
|
||||
pub zero: Option<bool>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Deserialize)]
|
||||
enum WeightJson {
|
||||
thin,
|
||||
extra_light,
|
||||
light,
|
||||
normal,
|
||||
medium,
|
||||
semibold,
|
||||
bold,
|
||||
extra_bold,
|
||||
black,
|
||||
}
|
||||
|
||||
struct Family {
|
||||
name: Arc<str>,
|
||||
font_features: FontFeatures,
|
||||
font_ids: Vec<FontId>,
|
||||
}
|
||||
|
||||
pub struct FontCache(RwLock<FontCacheState>);
|
||||
|
||||
pub struct FontCacheState {
|
||||
font_system: Arc<dyn PlatformFontSystem>,
|
||||
families: Vec<Family>,
|
||||
default_family: Option<FontFamilyId>,
|
||||
font_selections: HashMap<FontFamilyId, HashMap<(FontWeight, FontStyle), FontId>>,
|
||||
metrics: HashMap<FontId, FontMetrics>,
|
||||
wrapper_pool: HashMap<(FontId, Pixels), Vec<LineWrapper>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for FontCache {}
|
||||
|
||||
impl FontCache {
|
||||
pub fn new(fonts: Arc<dyn PlatformFontSystem>) -> Self {
|
||||
Self(RwLock::new(FontCacheState {
|
||||
font_system: fonts,
|
||||
families: Default::default(),
|
||||
default_family: None,
|
||||
font_selections: Default::default(),
|
||||
metrics: Default::default(),
|
||||
wrapper_pool: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
|
||||
self.0
|
||||
.read()
|
||||
.families
|
||||
.get(family_id.0)
|
||||
.ok_or_else(|| anyhow!("invalid family id"))
|
||||
.map(|family| family.name.clone())
|
||||
}
|
||||
|
||||
pub fn load_family(&self, names: &[&str], features: &FontFeatures) -> Result<FontFamilyId> {
|
||||
for name in names {
|
||||
let state = self.0.upgradable_read();
|
||||
|
||||
if let Some(ix) = state
|
||||
.families
|
||||
.iter()
|
||||
.position(|f| f.name.as_ref() == *name && f.font_features == *features)
|
||||
{
|
||||
return Ok(FontFamilyId(ix));
|
||||
}
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
|
||||
if let Ok(font_ids) = state.font_system.load_family(name, features) {
|
||||
if font_ids.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let family_id = FontFamilyId(state.families.len());
|
||||
for font_id in &font_ids {
|
||||
if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
|
||||
return Err(anyhow!("font must contain a glyph for the 'm' character"));
|
||||
}
|
||||
}
|
||||
|
||||
state.families.push(Family {
|
||||
name: Arc::from(*name),
|
||||
font_features: features.clone(),
|
||||
font_ids,
|
||||
});
|
||||
return Ok(family_id);
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"could not find a non-empty font family matching one of the given names"
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns an arbitrary font family that is available on the system.
|
||||
pub fn known_existing_family(&self) -> FontFamilyId {
|
||||
if let Some(family_id) = self.0.read().default_family {
|
||||
return family_id;
|
||||
}
|
||||
|
||||
let default_family = self
|
||||
.load_family(
|
||||
&["Courier", "Helvetica", "Arial", "Verdana"],
|
||||
&Default::default(),
|
||||
)
|
||||
.unwrap_or_else(|_| {
|
||||
let all_family_names = self.0.read().font_system.all_families();
|
||||
let all_family_names: Vec<_> = all_family_names
|
||||
.iter()
|
||||
.map(|string| string.as_str())
|
||||
.collect();
|
||||
self.load_family(&all_family_names, &Default::default())
|
||||
.expect("could not load any default font family")
|
||||
});
|
||||
|
||||
self.0.write().default_family = Some(default_family);
|
||||
default_family
|
||||
}
|
||||
|
||||
pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
|
||||
self.select_font(family_id, Default::default(), Default::default())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn select_font(
|
||||
&self,
|
||||
family_id: FontFamilyId,
|
||||
weight: FontWeight,
|
||||
style: FontStyle,
|
||||
) -> Result<FontId> {
|
||||
let inner = self.0.upgradable_read();
|
||||
if let Some(font_id) = inner
|
||||
.font_selections
|
||||
.get(&family_id)
|
||||
.and_then(|fonts| fonts.get(&(weight, style)))
|
||||
{
|
||||
Ok(*font_id)
|
||||
} else {
|
||||
let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
|
||||
let family = &inner.families[family_id.0];
|
||||
let font_id = inner
|
||||
.font_system
|
||||
.select_font(&family.font_ids, weight, style)
|
||||
.unwrap_or(family.font_ids[0]);
|
||||
inner
|
||||
.font_selections
|
||||
.entry(family_id)
|
||||
.or_default()
|
||||
.insert((weight, style), font_id);
|
||||
Ok(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&FontMetrics) -> T,
|
||||
T: 'static,
|
||||
{
|
||||
let state = self.0.upgradable_read();
|
||||
if let Some(metrics) = state.metrics.get(&font_id) {
|
||||
f(metrics)
|
||||
} else {
|
||||
let metrics = state.font_system.font_metrics(font_id);
|
||||
let metric = f(&metrics);
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
state.metrics.insert(font_id, metrics);
|
||||
metric
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
|
||||
let bounding_box = self.metric(font_id, |m| m.bounding_box);
|
||||
|
||||
let width = px(bounding_box.size.width) * self.em_size(font_id, font_size);
|
||||
let height = px(bounding_box.size.height) * self.em_size(font_id, font_size);
|
||||
Size { width, height }
|
||||
}
|
||||
|
||||
pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
let glyph_id;
|
||||
let bounds;
|
||||
{
|
||||
let state = self.0.read();
|
||||
glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
|
||||
bounds = state
|
||||
.font_system
|
||||
.typographic_bounds(font_id, glyph_id)
|
||||
.unwrap();
|
||||
}
|
||||
bounds.size.width * self.em_size(font_id, font_size)
|
||||
}
|
||||
|
||||
pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
let glyph_id;
|
||||
let advance;
|
||||
{
|
||||
let state = self.0.read();
|
||||
glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
|
||||
advance = state.font_system.advance(font_id, glyph_id).unwrap();
|
||||
}
|
||||
advance.x * self.em_size(font_id, font_size)
|
||||
}
|
||||
|
||||
pub fn line_height(&self, font_size: Pixels) -> Pixels {
|
||||
(font_size * 1.618).round()
|
||||
}
|
||||
|
||||
pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
self.em_size(font_id, font_size) * self.metric(font_id, |m| m.cap_height)
|
||||
}
|
||||
|
||||
pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
self.em_size(font_id, font_size) * self.metric(font_id, |m| m.x_height)
|
||||
}
|
||||
|
||||
pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
self.em_size(font_id, font_size) * self.metric(font_id, |m| m.ascent)
|
||||
}
|
||||
|
||||
pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
self.em_size(font_id, font_size) * self.metric(font_id, |m| -m.descent)
|
||||
}
|
||||
|
||||
pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
font_size / self.metric(font_id, |m| m.units_per_em as f32)
|
||||
}
|
||||
|
||||
pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
||||
let line_height = self.line_height(font_size);
|
||||
let ascent = self.ascent(font_id, font_size);
|
||||
let descent = self.descent(font_id, font_size);
|
||||
let padding_top = (line_height - ascent - descent) / 2.;
|
||||
padding_top + ascent
|
||||
}
|
||||
|
||||
pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: Pixels) -> LineWrapperHandle {
|
||||
let mut state = self.0.write();
|
||||
let wrappers = state.wrapper_pool.entry((font_id, font_size)).or_default();
|
||||
let wrapper = wrappers
|
||||
.pop()
|
||||
.unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
|
||||
LineWrapperHandle {
|
||||
wrapper: Some(wrapper),
|
||||
font_cache: self.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LineWrapperHandle {
|
||||
wrapper: Option<LineWrapper>,
|
||||
font_cache: Arc<FontCache>,
|
||||
}
|
||||
|
||||
impl Drop for LineWrapperHandle {
|
||||
fn drop(&mut self) {
|
||||
let mut state = self.font_cache.0.write();
|
||||
let wrapper = self.wrapper.take().unwrap();
|
||||
state
|
||||
.wrapper_pool
|
||||
.get_mut(&(wrapper.font_id, wrapper.font_size))
|
||||
.unwrap()
|
||||
.push(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LineWrapperHandle {
|
||||
type Target = LineWrapper;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.wrapper.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for LineWrapperHandle {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.wrapper.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FontMetrics {
|
||||
pub units_per_em: u32,
|
||||
pub ascent: f32,
|
||||
pub descent: f32,
|
||||
pub line_gap: f32,
|
||||
pub underline_position: f32,
|
||||
pub underline_thickness: f32,
|
||||
pub cap_height: f32,
|
||||
pub x_height: f32,
|
||||
pub bounding_box: Bounds<f32>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{FontStyle, FontWeight, Platform, TestPlatform};
|
||||
|
||||
#[test]
|
||||
fn test_select_font() {
|
||||
let platform = TestPlatform::new();
|
||||
let fonts = FontCache::new(platform.font_system());
|
||||
let arial = fonts
|
||||
.load_family(
|
||||
&["Arial"],
|
||||
&FontFeatures {
|
||||
calt: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let arial_regular = fonts
|
||||
.select_font(arial, FontWeight::default(), FontStyle::default())
|
||||
.unwrap();
|
||||
let arial_italic = fonts
|
||||
.select_font(arial, FontWeight::default(), FontStyle::Italic)
|
||||
.unwrap();
|
||||
let arial_bold = fonts
|
||||
.select_font(arial, FontWeight::BOLD, FontStyle::default())
|
||||
.unwrap();
|
||||
assert_ne!(arial_regular, arial_italic);
|
||||
assert_ne!(arial_regular, arial_bold);
|
||||
assert_ne!(arial_italic, arial_bold);
|
||||
|
||||
let arial_with_calt = fonts
|
||||
.load_family(
|
||||
&["Arial"],
|
||||
&FontFeatures {
|
||||
calt: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_ne!(arial_with_calt, arial);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use core::fmt::Debug;
|
||||
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
|
||||
use refineable::Refineable;
|
||||
use std::ops::{Add, Mul};
|
||||
|
||||
#[derive(Refineable, Default, Add, AddAssign, Sub, Mul, Div, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[refineable(debug)]
|
||||
#[repr(C)]
|
||||
pub struct Point<T: Clone + Debug> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
pub fn point<T: Clone + Debug>(x: T, y: T) -> Point<T> {
|
||||
Point { x, y }
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug> Point<T> {
|
||||
pub fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug> Clone for Point<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
x: self.x.clone(),
|
||||
y: self.y.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
|
||||
|
||||
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
|
||||
|
||||
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq)]
|
||||
#[refineable(debug)]
|
||||
pub struct Size<T: Clone + Debug> {
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn full() -> Self {
|
||||
Self {
|
||||
width: relative(1.),
|
||||
height: relative(1.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
width: px(0.).into(),
|
||||
height: px(0.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
width: Length::Auto,
|
||||
height: Length::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug, PartialEq)]
|
||||
#[refineable(debug)]
|
||||
pub struct Bounds<T: Clone + Debug> {
|
||||
pub origin: Point<T>,
|
||||
pub size: Size<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Copy + Add<T, Output = T>> Bounds<T> {
|
||||
pub fn upper_right(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x + self.size.width,
|
||||
y: self.origin.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Edges<T: Clone + Debug> {
|
||||
pub top: T,
|
||||
pub right: T,
|
||||
pub bottom: T,
|
||||
pub left: T,
|
||||
}
|
||||
|
||||
impl Edges<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
top: Length::Auto,
|
||||
right: Length::Auto,
|
||||
bottom: Length::Auto,
|
||||
left: Length::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.).into(),
|
||||
right: px(0.).into(),
|
||||
bottom: px(0.).into(),
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.).into(),
|
||||
right: px(0.).into(),
|
||||
bottom: px(0.).into(),
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<AbsoluteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.).into(),
|
||||
right: px(0.).into(),
|
||||
bottom: px(0.).into(),
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
|
||||
Edges {
|
||||
top: self.top.to_pixels(rem_size),
|
||||
right: self.right.to_pixels(rem_size),
|
||||
bottom: self.bottom.to_pixels(rem_size),
|
||||
left: self.left.to_pixels(rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<Pixels> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.top == px(0.) && self.right == px(0.) && self.bottom == px(0.) && self.left == px(0.)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Pixels(pub(crate) f32);
|
||||
|
||||
impl Mul<f32> for Pixels {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, other: f32) -> Pixels {
|
||||
Pixels(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Pixels> for Pixels {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, rhs: Pixels) -> Self::Output {
|
||||
Pixels(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixels {
|
||||
pub fn round(&self) -> Self {
|
||||
Self(self.0.round())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pixels {}
|
||||
|
||||
impl Ord for Pixels {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0.partial_cmp(&other.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Pixels {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for Pixels {}
|
||||
unsafe impl bytemuck::Zeroable for Pixels {}
|
||||
|
||||
impl Debug for Pixels {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} px", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for f32 {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
pixels.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Pixels> for f32 {
|
||||
fn from(pixels: &Pixels) -> Self {
|
||||
pixels.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
|
||||
pub struct Rems(f32);
|
||||
|
||||
impl Mul<Pixels> for Rems {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, other: Pixels) -> Pixels {
|
||||
Pixels(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Rems {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} rem", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum AbsoluteLength {
|
||||
Pixels(Pixels),
|
||||
Rems(Rems),
|
||||
}
|
||||
|
||||
impl From<Pixels> for AbsoluteLength {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
AbsoluteLength::Pixels(pixels)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rems> for AbsoluteLength {
|
||||
fn from(rems: Rems) -> Self {
|
||||
AbsoluteLength::Rems(rems)
|
||||
}
|
||||
}
|
||||
|
||||
impl AbsoluteLength {
|
||||
pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => *pixels,
|
||||
AbsoluteLength::Rems(rems) => *rems * rem_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AbsoluteLength {
|
||||
fn default() -> Self {
|
||||
px(0.).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DefiniteLength {
|
||||
Absolute(AbsoluteLength),
|
||||
/// A fraction of the parent's size between 0 and 1.
|
||||
Fraction(f32),
|
||||
}
|
||||
|
||||
impl Debug for DefiniteLength {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
|
||||
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for DefiniteLength {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
Self::Absolute(pixels.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rems> for DefiniteLength {
|
||||
fn from(rems: Rems) -> Self {
|
||||
Self::Absolute(rems.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteLength> for DefiniteLength {
|
||||
fn from(length: AbsoluteLength) -> Self {
|
||||
Self::Absolute(length)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefiniteLength {
|
||||
fn default() -> Self {
|
||||
Self::Absolute(AbsoluteLength::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A length that can be defined in pixels, rems, percent of parent, or auto.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Length {
|
||||
Definite(DefiniteLength),
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Debug for Length {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
|
||||
Length::Auto => write!(f, "auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
|
||||
DefiniteLength::Fraction(fraction).into()
|
||||
}
|
||||
|
||||
pub fn rems(rems: f32) -> Rems {
|
||||
Rems(rems).into()
|
||||
}
|
||||
|
||||
pub fn px(pixels: f32) -> Pixels {
|
||||
Pixels(pixels).into()
|
||||
}
|
||||
|
||||
pub fn auto() -> Length {
|
||||
Length::Auto
|
||||
}
|
||||
|
||||
impl From<Pixels> for Length {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
Self::Definite(pixels.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rems> for Length {
|
||||
fn from(rems: Rems) -> Self {
|
||||
Self::Definite(rems.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefiniteLength> for Length {
|
||||
fn from(length: DefiniteLength) -> Self {
|
||||
Self::Definite(length)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteLength> for Length {
|
||||
fn from(length: AbsoluteLength) -> Self {
|
||||
Self::Definite(length.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Length {
|
||||
fn default() -> Self {
|
||||
Self::Definite(DefiniteLength::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for Length {
|
||||
fn from(_: ()) -> Self {
|
||||
Self::Definite(DefiniteLength::default())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
mod app;
|
||||
mod color;
|
||||
mod element;
|
||||
mod elements;
|
||||
mod fonts;
|
||||
mod geometry;
|
||||
mod platform;
|
||||
mod renderer;
|
||||
mod scene;
|
||||
mod style;
|
||||
mod taffy;
|
||||
mod text;
|
||||
mod window;
|
||||
|
||||
use anyhow::Result;
|
||||
pub use app::*;
|
||||
pub use color::*;
|
||||
pub use element::*;
|
||||
pub use elements::*;
|
||||
pub use fonts::*;
|
||||
pub use geometry::*;
|
||||
pub use platform::*;
|
||||
pub use scene::*;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
pub use style::*;
|
||||
pub use taffy::LayoutId;
|
||||
use taffy::TaffyLayoutEngine;
|
||||
use text::*;
|
||||
pub use util::arc_cow::ArcCow;
|
||||
pub use window::*;
|
||||
|
||||
pub trait Context {
|
||||
type EntityContext<'a, 'w, T: 'static>;
|
||||
|
||||
fn entity<T: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Handle<T>;
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> R;
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
impl std::fmt::Debug for SharedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Reference<'a, T> {
|
||||
Immutable(&'a T),
|
||||
Mutable(&'a mut T),
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Reference<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Reference::Immutable(target) => target,
|
||||
Reference::Mutable(target) => target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for Reference<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self {
|
||||
Reference::Immutable(_) => {
|
||||
panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
|
||||
}
|
||||
Reference::Mutable(target) => target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct Workspace {
|
||||
left_panel: AnyView<Self>,
|
||||
}
|
||||
|
||||
fn workspace(cx: &mut WindowContext) -> View<Workspace> {
|
||||
let workspace = cx.entity(|cx| Workspace {
|
||||
left_panel: collab_panel(cx).into_any(),
|
||||
});
|
||||
view(workspace, |workspace, cx| {
|
||||
div().child(workspace.left_panel.clone())
|
||||
})
|
||||
}
|
||||
|
||||
struct CollabPanel {
|
||||
filter_editor: Handle<editor::Editor>,
|
||||
}
|
||||
|
||||
fn collab_panel(cx: &mut WindowContext) -> View<CollabPanel> {
|
||||
let panel = cx.entity(|cx| CollabPanel::new(cx));
|
||||
view(panel, |panel, cx| {
|
||||
div().child(div()).child(
|
||||
field(panel.filter_editor.clone()).placeholder_text("Search channels, contacts"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
impl CollabPanel {
|
||||
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
filter_editor: cx.entity(|cx| editor::Editor::new(cx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Editor {}
|
||||
|
||||
impl Editor {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut cx = AppContext::test();
|
||||
|
||||
cx.open_window(WindowOptions::default(), |cx| workspace(cx));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
mod test;
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
|
||||
LineLayout, Pixels, Point, RunStyle, SharedString,
|
||||
};
|
||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub use test::*;
|
||||
|
||||
pub trait Platform {
|
||||
fn font_system(&self) -> Arc<dyn PlatformFontSystem>;
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
}
|
||||
|
||||
pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {}
|
||||
|
||||
pub trait PlatformFontSystem: Send + Sync {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
|
||||
fn all_families(&self) -> Vec<String>;
|
||||
fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
|
||||
fn select_font(
|
||||
&self,
|
||||
font_ids: &[FontId],
|
||||
weight: FontWeight,
|
||||
style: FontStyle,
|
||||
) -> anyhow::Result<FontId>;
|
||||
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
|
||||
fn typographic_bounds(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
glyph_id: GlyphId,
|
||||
) -> anyhow::Result<Bounds<Pixels>>;
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Point<Pixels>>;
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_shift: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
options: RasterizationOptions,
|
||||
) -> Option<(Bounds<u32>, Vec<u8>)>;
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)]) -> LineLayout;
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
width: Pixels,
|
||||
) -> Vec<usize>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RasterizationOptions {
|
||||
Alpha,
|
||||
Bgra,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowOptions {
|
||||
pub bounds: WindowBounds,
|
||||
pub titlebar: Option<TitlebarOptions>,
|
||||
pub center: bool,
|
||||
pub focus: bool,
|
||||
pub show: bool,
|
||||
pub kind: WindowKind,
|
||||
pub is_movable: bool,
|
||||
}
|
||||
|
||||
impl Default for WindowOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bounds: WindowBounds::default(),
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: Default::default(),
|
||||
appears_transparent: Default::default(),
|
||||
traffic_light_position: Default::default(),
|
||||
}),
|
||||
center: false,
|
||||
focus: true,
|
||||
show: true,
|
||||
kind: WindowKind::Normal,
|
||||
is_movable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TitlebarOptions {
|
||||
pub title: Option<SharedString>,
|
||||
pub appears_transparent: bool,
|
||||
pub traffic_light_position: Option<Point<f32>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Appearance {
|
||||
Light,
|
||||
VibrantLight,
|
||||
Dark,
|
||||
VibrantDark,
|
||||
}
|
||||
|
||||
impl Default for Appearance {
|
||||
fn default() -> Self {
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum WindowKind {
|
||||
Normal,
|
||||
PopUp,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub enum WindowBounds {
|
||||
Fullscreen,
|
||||
#[default]
|
||||
Maximized,
|
||||
Fixed(Bounds<f32>),
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use super::Platform;
|
||||
|
||||
pub struct TestPlatform;
|
||||
|
||||
impl TestPlatform {
|
||||
pub fn new() -> Self {
|
||||
TestPlatform
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for TestPlatform {
|
||||
fn font_system(&self) -> std::sync::Arc<dyn crate::PlatformFontSystem> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: crate::AnyWindowHandle,
|
||||
options: crate::WindowOptions,
|
||||
) -> Box<dyn crate::PlatformWindow> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||
|
||||
use super::{Scene, Size};
|
||||
|
||||
pub struct Renderer {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
surface: wgpu::Surface,
|
||||
surface_config: wgpu::SurfaceConfiguration,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
vertex_count: u32,
|
||||
}
|
||||
|
||||
pub trait Window: HasRawWindowHandle + HasRawDisplayHandle {
|
||||
fn inner_size(&self) -> Size<u32>;
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub async fn new<W>(window: &W) -> Self
|
||||
where
|
||||
W: Window,
|
||||
{
|
||||
let instance = wgpu::Instance::new(Default::default());
|
||||
let surface = unsafe { instance.create_surface(window).unwrap() };
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
width: window.inner_size().width,
|
||||
height: window.inner_size().height,
|
||||
|
||||
// "FIFO" mode renders frames in queue synced with the display's refresh rate.
|
||||
// Avoids screen tearing but may not offer the lowest latency. Ideal when image
|
||||
// quality takes priority over input latency.
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
|
||||
// Use the Premultiplied alpha mode. With premultiplication, the color components
|
||||
// are multiplied by the alpha value before storage or blending, meaning calculations
|
||||
// with colors already factor in the influence of alpha. This typically results
|
||||
// in better performance and avoids a separate multiplication operation during blending.
|
||||
alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
|
||||
|
||||
// Specify the color formats for the views the surface can have.
|
||||
// In this case, the format is BGRA (blue, green, red, alpha) with unsigned
|
||||
// normalised integers in the 8-bit range and the color space is sRGB (standard RGB).
|
||||
// sRGB is the standard color space for displaying images and video on digital displays,
|
||||
// as it optimises color accuracy and consistency.
|
||||
view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb],
|
||||
};
|
||||
|
||||
surface.configure(&device, &surface_config);
|
||||
|
||||
let vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Vertex Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shader.vert.wgsl").into()),
|
||||
});
|
||||
|
||||
let fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Fragment Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shader.frag.wgsl").into()),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
size: 0,
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &vs_module,
|
||||
entry_point: "main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &fs_module,
|
||||
entry_point: "main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
surface,
|
||||
surface_config,
|
||||
pipeline,
|
||||
vertex_buffer,
|
||||
vertex_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, scene: &Scene) {
|
||||
let frame = self.surface.get_current_texture().unwrap();
|
||||
let view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
self.queue.write_buffer(
|
||||
&self.vertex_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&scene.opaque_primitives().quads),
|
||||
);
|
||||
self.vertex_count = scene.opaque_primitives().quads.len() as u32;
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||
render_pass.draw(0..self.vertex_count, 0..1);
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use super::{Bounds, Hsla, Pixels, Point};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use plane_split::BspSplitter;
|
||||
|
||||
pub struct Scene {
|
||||
opaque_primitives: PrimitiveBatch,
|
||||
transparent_primitives: slotmap::SlotMap<slotmap::DefaultKey, Primitive>,
|
||||
splitter: BspSplitter<slotmap::DefaultKey>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub fn new() -> Scene {
|
||||
Scene {
|
||||
opaque_primitives: PrimitiveBatch::default(),
|
||||
transparent_primitives: slotmap::SlotMap::new(),
|
||||
splitter: BspSplitter::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, primitive: impl Into<Primitive>, is_transparent: bool) {
|
||||
if is_transparent {
|
||||
self.transparent_primitives.insert(primitive.into());
|
||||
} else {
|
||||
match primitive.into() {
|
||||
Primitive::Quad(quad) => self.opaque_primitives.quads.push(quad),
|
||||
Primitive::Glyph(glyph) => self.opaque_primitives.glyphs.push(glyph),
|
||||
Primitive::Underline(underline) => {
|
||||
self.opaque_primitives.underlines.push(underline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opaque_primitives(&self) -> &PrimitiveBatch {
|
||||
&self.opaque_primitives
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Primitive {
|
||||
Quad(Quad),
|
||||
Glyph(Glyph),
|
||||
Underline(Underline),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
match self {
|
||||
Primitive::Quad(quad) => {
|
||||
quad.background.is_transparent() && quad.border_color.is_transparent()
|
||||
}
|
||||
Primitive::Glyph(glyph) => glyph.color.is_transparent(),
|
||||
Primitive::Underline(underline) => underline.color.is_transparent(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PrimitiveBatch {
|
||||
pub quads: Vec<Quad>,
|
||||
pub glyphs: Vec<Glyph>,
|
||||
pub underlines: Vec<Underline>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
pub order: f32,
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub background: Hsla,
|
||||
pub border_color: Hsla,
|
||||
pub corner_radius: Pixels,
|
||||
pub border_left: Pixels,
|
||||
pub border_right: Pixels,
|
||||
pub border_top: Pixels,
|
||||
pub border_bottom: Pixels,
|
||||
}
|
||||
|
||||
impl Quad {
|
||||
pub fn vertices(&self) -> impl Iterator<Item = Point<Pixels>> {
|
||||
let x1 = self.bounds.origin.x;
|
||||
let y1 = self.bounds.origin.y;
|
||||
let x2 = x1 + self.bounds.size.width;
|
||||
let y2 = y1 + self.bounds.size.height;
|
||||
[
|
||||
Point::new(x1, y1),
|
||||
Point::new(x2, y1),
|
||||
Point::new(x2, y2),
|
||||
Point::new(x1, y2),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for Quad {}
|
||||
|
||||
unsafe impl Pod for Quad {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct GlyphId(u32);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Glyph {
|
||||
pub id: GlyphId,
|
||||
pub position: Point<Pixels>,
|
||||
pub color: Hsla,
|
||||
pub index: usize,
|
||||
pub is_emoji: bool,
|
||||
}
|
||||
|
||||
impl From<Quad> for Primitive {
|
||||
fn from(quad: Quad) -> Self {
|
||||
Primitive::Quad(quad)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Glyph> for Primitive {
|
||||
fn from(glyph: Glyph) -> Self {
|
||||
Primitive::Glyph(glyph)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Underline {
|
||||
pub origin: Point<Pixels>,
|
||||
pub width: Pixels,
|
||||
pub thickness: Pixels,
|
||||
pub color: Hsla,
|
||||
pub squiggly: bool,
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for Underline {}
|
||||
|
||||
unsafe impl Pod for Underline {}
|
||||
|
||||
impl From<Underline> for Primitive {
|
||||
fn from(underline: Underline) -> Self {
|
||||
Primitive::Underline(underline)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
use super::{
|
||||
rems, AbsoluteLength, Bounds, DefiniteLength, Edges, EdgesRefinement, FontStyle, FontWeight,
|
||||
Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, Size,
|
||||
SizeRefinement, ViewContext, WindowContext,
|
||||
};
|
||||
use crate::{FontCache};
|
||||
use refineable::Refineable;
|
||||
pub use taffy::style::{
|
||||
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
|
||||
Overflow, Position,
|
||||
};
|
||||
|
||||
#[derive(Clone, Refineable, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Style {
|
||||
/// What layout strategy should be used?
|
||||
pub display: Display,
|
||||
|
||||
// Overflow properties
|
||||
/// How children overflowing their container should affect layout
|
||||
#[refineable]
|
||||
pub overflow: Point<Overflow>,
|
||||
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
|
||||
pub scrollbar_width: f32,
|
||||
|
||||
// Position properties
|
||||
/// What should the `position` value of this struct use as a base offset?
|
||||
pub position: Position,
|
||||
/// How should the position of this element be tweaked relative to the layout defined?
|
||||
#[refineable]
|
||||
pub inset: Edges<Length>,
|
||||
|
||||
// Size properies
|
||||
/// Sets the initial size of the item
|
||||
#[refineable]
|
||||
pub size: Size<Length>,
|
||||
/// Controls the minimum size of the item
|
||||
#[refineable]
|
||||
pub min_size: Size<Length>,
|
||||
/// Controls the maximum size of the item
|
||||
#[refineable]
|
||||
pub max_size: Size<Length>,
|
||||
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
|
||||
pub aspect_ratio: Option<f32>,
|
||||
|
||||
// Spacing Properties
|
||||
/// How large should the margin be on each side?
|
||||
#[refineable]
|
||||
pub margin: Edges<Length>,
|
||||
/// How large should the padding be on each side?
|
||||
#[refineable]
|
||||
pub padding: Edges<DefiniteLength>,
|
||||
/// How large should the border be on each side?
|
||||
#[refineable]
|
||||
pub border_widths: Edges<AbsoluteLength>,
|
||||
|
||||
// Alignment properties
|
||||
/// How this node's children aligned in the cross/block axis?
|
||||
pub align_items: Option<AlignItems>,
|
||||
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
|
||||
pub align_self: Option<AlignSelf>,
|
||||
/// How should content contained within this item be aligned in the cross/block axis
|
||||
pub align_content: Option<AlignContent>,
|
||||
/// How should contained within this item be aligned in the main/inline axis
|
||||
pub justify_content: Option<JustifyContent>,
|
||||
/// How large should the gaps between items in a flex container be?
|
||||
#[refineable]
|
||||
pub gap: Size<DefiniteLength>,
|
||||
|
||||
// Flexbox properies
|
||||
/// Which direction does the main axis flow in?
|
||||
pub flex_direction: FlexDirection,
|
||||
/// Should elements wrap, or stay in a single line?
|
||||
pub flex_wrap: FlexWrap,
|
||||
/// Sets the initial main axis size of the item
|
||||
pub flex_basis: Length,
|
||||
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
|
||||
pub flex_grow: f32,
|
||||
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
|
||||
pub flex_shrink: f32,
|
||||
|
||||
/// The fill color of this element
|
||||
pub fill: Option<Fill>,
|
||||
|
||||
/// The border color of this element
|
||||
pub border_color: Option<Hsla>,
|
||||
|
||||
/// The radius of the corners of this element
|
||||
#[refineable]
|
||||
pub corner_radii: CornerRadii,
|
||||
|
||||
/// The color of text within this element. Cascades to children unless overridden.
|
||||
pub text_color: Option<Hsla>,
|
||||
|
||||
/// The font size in rems.
|
||||
pub font_size: Option<Rems>,
|
||||
|
||||
pub font_family: Option<SharedString>,
|
||||
|
||||
pub font_weight: Option<FontWeight>,
|
||||
|
||||
pub font_style: Option<FontStyle>,
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct TextStyle {
|
||||
pub color: Hsla,
|
||||
pub font_family: SharedString,
|
||||
pub font_size: Rems,
|
||||
pub font_weight: FontWeight,
|
||||
pub font_style: FontStyle,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn highlight(mut self, style: HighlightStyle, _font_cache: &FontCache) -> Result<Self> {
|
||||
if let Some(weight) = style.font_weight {
|
||||
self.font_weight = weight;
|
||||
}
|
||||
if let Some(style) = style.font_style {
|
||||
self.font_style = style;
|
||||
}
|
||||
|
||||
if let Some(color) = style.color {
|
||||
self.color = self.color.blend(color);
|
||||
}
|
||||
|
||||
if let Some(factor) = style.fade_out {
|
||||
self.color.fade_out(factor);
|
||||
}
|
||||
|
||||
if let Some(underline) = style.underline {
|
||||
self.underline = Some(underline);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn to_run(&self) -> RunStyle {
|
||||
RunStyle {
|
||||
font_id: todo!(),
|
||||
color: self.color,
|
||||
underline: self.underline.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct HighlightStyle {
|
||||
pub color: Option<Hsla>,
|
||||
pub font_weight: Option<FontWeight>,
|
||||
pub font_style: Option<FontStyle>,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
pub fade_out: Option<f32>,
|
||||
}
|
||||
|
||||
impl Eq for HighlightStyle {}
|
||||
|
||||
impl Style {
|
||||
pub fn text_style(&self, _cx: &WindowContext) -> Option<TextStyleRefinement> {
|
||||
if self.text_color.is_none()
|
||||
&& self.font_size.is_none()
|
||||
&& self.font_family.is_none()
|
||||
&& self.font_weight.is_none()
|
||||
&& self.font_style.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(TextStyleRefinement {
|
||||
color: self.text_color,
|
||||
font_family: self.font_family.clone(),
|
||||
font_size: self.font_size,
|
||||
font_weight: self.font_weight,
|
||||
font_style: self.font_style,
|
||||
underline: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Paints the background of an element styled with this style.
|
||||
pub fn paint_background<V: 'static>(&self, _bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||
let _rem_size = cx.rem_size();
|
||||
if let Some(_color) = self.fill.as_ref().and_then(Fill::color) {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Paints the foreground of an element styled with this style.
|
||||
pub fn paint_foreground<V: 'static>(&self, _bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
if let Some(_color) = self.border_color {
|
||||
let border = self.border_widths.to_pixels(rem_size);
|
||||
if !border.is_empty() {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
display: Display::Block,
|
||||
overflow: Point {
|
||||
x: Overflow::Visible,
|
||||
y: Overflow::Visible,
|
||||
},
|
||||
scrollbar_width: 0.0,
|
||||
position: Position::Relative,
|
||||
inset: Edges::auto(),
|
||||
margin: Edges::<Length>::zero(),
|
||||
padding: Edges::<DefiniteLength>::zero(),
|
||||
border_widths: Edges::<AbsoluteLength>::zero(),
|
||||
size: Size::auto(),
|
||||
min_size: Size::auto(),
|
||||
max_size: Size::auto(),
|
||||
aspect_ratio: None,
|
||||
gap: Size::zero(),
|
||||
// Aligment
|
||||
align_items: None,
|
||||
align_self: None,
|
||||
align_content: None,
|
||||
justify_content: None,
|
||||
// Flexbox
|
||||
flex_direction: FlexDirection::Row,
|
||||
flex_wrap: FlexWrap::NoWrap,
|
||||
flex_grow: 0.0,
|
||||
flex_shrink: 1.0,
|
||||
flex_basis: Length::Auto,
|
||||
fill: None,
|
||||
border_color: None,
|
||||
corner_radii: CornerRadii::default(),
|
||||
text_color: None,
|
||||
font_size: Some(rems(1.)),
|
||||
font_family: None,
|
||||
font_weight: None,
|
||||
font_style: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[refineable(debug)]
|
||||
pub struct UnderlineStyle {
|
||||
pub thickness: Pixels,
|
||||
pub color: Option<Hsla>,
|
||||
pub squiggly: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Fill {
|
||||
Color(Hsla),
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn color(&self) -> Option<Hsla> {
|
||||
match self {
|
||||
Fill::Color(color) => Some(*color),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Fill {
|
||||
fn default() -> Self {
|
||||
Self::Color(Hsla::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for Fill {
|
||||
fn from(color: Hsla) -> Self {
|
||||
Self::Color(color)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Refineable, Default, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct CornerRadii {
|
||||
top_left: AbsoluteLength,
|
||||
top_right: AbsoluteLength,
|
||||
bottom_left: AbsoluteLength,
|
||||
bottom_right: AbsoluteLength,
|
||||
}
|
||||
|
||||
impl From<TextStyle> for HighlightStyle {
|
||||
fn from(other: TextStyle) -> Self {
|
||||
Self::from(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TextStyle> for HighlightStyle {
|
||||
fn from(other: &TextStyle) -> Self {
|
||||
Self {
|
||||
color: Some(other.color),
|
||||
font_weight: Some(other.font_weight),
|
||||
font_style: Some(other.font_style),
|
||||
underline: other.underline.clone(),
|
||||
fade_out: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HighlightStyle {
|
||||
pub fn highlight(&mut self, other: HighlightStyle) {
|
||||
match (self.color, other.color) {
|
||||
(Some(self_color), Some(other_color)) => {
|
||||
self.color = Some(Hsla::blend(other_color, self_color));
|
||||
}
|
||||
(None, Some(other_color)) => {
|
||||
self.color = Some(other_color);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if other.font_weight.is_some() {
|
||||
self.font_weight = other.font_weight;
|
||||
}
|
||||
|
||||
if other.font_style.is_some() {
|
||||
self.font_style = other.font_style;
|
||||
}
|
||||
|
||||
if other.underline.is_some() {
|
||||
self.underline = other.underline;
|
||||
}
|
||||
|
||||
match (other.fade_out, self.fade_out) {
|
||||
(Some(source_fade), None) => self.fade_out = Some(source_fade),
|
||||
(Some(source_fade), Some(dest_fade)) => {
|
||||
self.fade_out = Some((dest_fade * (1. + source_fade)).clamp(0., 1.));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for HighlightStyle {
|
||||
fn from(color: Hsla) -> Self {
|
||||
Self {
|
||||
color: Some(color),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
use super::{
|
||||
AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
|
||||
Style,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
pub use taffy::tree::NodeId as LayoutId;
|
||||
pub use taffy::*;
|
||||
pub struct TaffyLayoutEngine(Taffy);
|
||||
|
||||
impl TaffyLayoutEngine {
|
||||
pub fn new() -> Self {
|
||||
TaffyLayoutEngine(Taffy::new())
|
||||
}
|
||||
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
rem_size: Pixels,
|
||||
children: &[LayoutId],
|
||||
) -> Result<LayoutId> {
|
||||
let style = style.to_taffy(rem_size);
|
||||
if children.is_empty() {
|
||||
Ok(self.0.new_leaf(style)?)
|
||||
} else {
|
||||
Ok(self.0.new_with_children(style, children)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
|
||||
Ok(self.0.layout(id).map(Into::into)?)
|
||||
}
|
||||
}
|
||||
|
||||
trait ToTaffy<Output> {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> Output;
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::Style> for Style {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
|
||||
taffy::style::Style {
|
||||
display: self.display,
|
||||
overflow: self.overflow.clone().into(),
|
||||
scrollbar_width: self.scrollbar_width,
|
||||
position: self.position,
|
||||
inset: self.inset.to_taffy(rem_size),
|
||||
size: self.size.to_taffy(rem_size),
|
||||
min_size: self.min_size.to_taffy(rem_size),
|
||||
max_size: self.max_size.to_taffy(rem_size),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
margin: self.margin.to_taffy(rem_size),
|
||||
padding: self.padding.to_taffy(rem_size),
|
||||
border: self.border_widths.to_taffy(rem_size),
|
||||
align_items: self.align_items,
|
||||
align_self: self.align_self,
|
||||
align_content: self.align_content,
|
||||
justify_content: self.justify_content,
|
||||
gap: self.gap.to_taffy(rem_size),
|
||||
flex_direction: self.flex_direction,
|
||||
flex_wrap: self.flex_wrap,
|
||||
flex_basis: self.flex_basis.to_taffy(rem_size),
|
||||
flex_grow: self.flex_grow,
|
||||
flex_shrink: self.flex_shrink,
|
||||
..Default::default() // Ignore grid properties for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl ToTaffy for Bounds<Length> {
|
||||
// type Output = taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto>;
|
||||
|
||||
// fn to_taffy(
|
||||
// &self,
|
||||
// rem_size: Pixels,
|
||||
// ) -> taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto> {
|
||||
// taffy::prelude::Bounds {
|
||||
// origin: self.origin.to_taffy(rem_size),
|
||||
// size: self.size.to_taffy(rem_size),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
|
||||
match self {
|
||||
Length::Definite(length) => length.to_taffy(rem_size),
|
||||
Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::Dimension> for Length {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
|
||||
match self {
|
||||
Length::Definite(length) => length.to_taffy(rem_size),
|
||||
Length::Auto => taffy::prelude::Dimension::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => match length {
|
||||
AbsoluteLength::Pixels(pixels) => {
|
||||
taffy::style::LengthPercentage::Length(pixels.into())
|
||||
}
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
|
||||
}
|
||||
},
|
||||
DefiniteLength::Fraction(fraction) => {
|
||||
taffy::style::LengthPercentage::Percent(*fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => match length {
|
||||
AbsoluteLength::Pixels(pixels) => {
|
||||
taffy::style::LengthPercentageAuto::Length(pixels.into())
|
||||
}
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::LengthPercentageAuto::Length((*rems * rem_size).into())
|
||||
}
|
||||
},
|
||||
DefiniteLength::Fraction(fraction) => {
|
||||
taffy::style::LengthPercentageAuto::Percent(*fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => match length {
|
||||
AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::Length(pixels.into()),
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::Dimension::Length((*rems * rem_size).into())
|
||||
}
|
||||
},
|
||||
DefiniteLength::Fraction(fraction) => taffy::style::Dimension::Percent(*fraction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(pixels.into()),
|
||||
AbsoluteLength::Rems(rems) => {
|
||||
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, T2: Clone + Debug> From<taffy::geometry::Point<T>> for Point<T2>
|
||||
where
|
||||
T: Into<T2>,
|
||||
{
|
||||
fn from(point: taffy::geometry::Point<T>) -> Point<T2> {
|
||||
Point {
|
||||
x: point.x.into(),
|
||||
y: point.y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug, T2> Into<taffy::geometry::Point<T2>> for Point<T>
|
||||
where
|
||||
T: Into<T2>,
|
||||
{
|
||||
fn into(self) -> taffy::geometry::Point<T2> {
|
||||
taffy::geometry::Point {
|
||||
x: self.x.into(),
|
||||
y: self.y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToTaffy<U> + Clone + Debug, U> ToTaffy<taffy::geometry::Size<U>> for Size<T> {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Size<U> {
|
||||
taffy::geometry::Size {
|
||||
width: self.width.to_taffy(rem_size).into(),
|
||||
height: self.height.to_taffy(rem_size).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ToTaffy<taffy::geometry::Rect<U>> for Edges<T>
|
||||
where
|
||||
T: ToTaffy<U> + Clone + Debug,
|
||||
{
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Rect<U> {
|
||||
taffy::geometry::Rect {
|
||||
top: self.top.to_taffy(rem_size).into(),
|
||||
right: self.right.to_taffy(rem_size).into(),
|
||||
bottom: self.bottom.to_taffy(rem_size).into(),
|
||||
left: self.left.to_taffy(rem_size).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
|
||||
where
|
||||
S: Into<T>,
|
||||
{
|
||||
fn from(value: taffy::geometry::Size<S>) -> Self {
|
||||
Self {
|
||||
width: value.width.into(),
|
||||
height: value.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&taffy::tree::Layout> for Layout {
|
||||
fn from(layout: &taffy::tree::Layout) -> Self {
|
||||
Layout {
|
||||
order: layout.order,
|
||||
bounds: Bounds {
|
||||
origin: layout.location.into(),
|
||||
size: layout.size.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Pixels {
|
||||
fn from(pixels: f32) -> Self {
|
||||
Pixels(pixels)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,852 @@
|
||||
use crate::{black, px};
|
||||
|
||||
use super::{
|
||||
point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformFontSystem, Point, UnderlineStyle,
|
||||
WindowContext,
|
||||
};
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
iter,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub struct TextLayoutCache {
|
||||
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
||||
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
||||
fonts: Arc<dyn PlatformFontSystem>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RunStyle {
|
||||
pub color: Hsla,
|
||||
pub font_id: FontId,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
}
|
||||
|
||||
impl TextLayoutCache {
|
||||
pub fn new(fonts: Arc<dyn PlatformFontSystem>) -> Self {
|
||||
Self {
|
||||
prev_frame: Mutex::new(HashMap::new()),
|
||||
curr_frame: RwLock::new(HashMap::new()),
|
||||
fonts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_frame(&self) {
|
||||
let mut prev_frame = self.prev_frame.lock();
|
||||
let mut curr_frame = self.curr_frame.write();
|
||||
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
||||
curr_frame.clear();
|
||||
}
|
||||
|
||||
pub fn layout_str<'a>(
|
||||
&'a self,
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &'a [(usize, RunStyle)],
|
||||
) -> Line {
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
font_size,
|
||||
runs,
|
||||
} as &dyn CacheKey;
|
||||
let curr_frame = self.curr_frame.upgradable_read();
|
||||
if let Some(layout) = curr_frame.get(key) {
|
||||
return Line::new(layout.clone(), runs);
|
||||
}
|
||||
|
||||
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
|
||||
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
|
||||
curr_frame.insert(key, layout.clone());
|
||||
Line::new(layout, runs)
|
||||
} else {
|
||||
let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
|
||||
let key = CacheKeyValue {
|
||||
text: text.into(),
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
};
|
||||
curr_frame.insert(key, layout.clone());
|
||||
Line::new(layout, runs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait CacheKey {
|
||||
fn key(&self) -> CacheKeyRef;
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for (dyn CacheKey + 'a) {
|
||||
fn eq(&self, other: &dyn CacheKey) -> bool {
|
||||
self.key() == other.key()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eq for (dyn CacheKey + 'a) {}
|
||||
|
||||
impl<'a> Hash for (dyn CacheKey + 'a) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
struct CacheKeyValue {
|
||||
text: String,
|
||||
font_size: Pixels,
|
||||
runs: SmallVec<[(usize, RunStyle); 1]>,
|
||||
}
|
||||
|
||||
impl CacheKey for CacheKeyValue {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
CacheKeyRef {
|
||||
text: self.text.as_str(),
|
||||
font_size: self.font_size,
|
||||
runs: self.runs.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CacheKeyValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key().eq(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CacheKeyValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
|
||||
fn borrow(&self) -> &(dyn CacheKey + 'a) {
|
||||
self as &dyn CacheKey
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct CacheKeyRef<'a> {
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &'a [(usize, RunStyle)],
|
||||
}
|
||||
|
||||
impl<'a> CacheKey for CacheKeyRef<'a> {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for CacheKeyRef<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.text == other.text
|
||||
&& self.font_size == other.font_size
|
||||
&& self.runs.len() == other.runs.len()
|
||||
&& self.runs.iter().zip(other.runs.iter()).all(
|
||||
|((len_a, style_a), (len_b, style_b))| {
|
||||
len_a == len_b && style_a.font_id == style_b.font_id
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for CacheKeyRef<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.text.hash(state);
|
||||
self.font_size.hash(state);
|
||||
for (len, style_id) in self.runs {
|
||||
len.hash(state);
|
||||
style_id.font_id.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Line {
|
||||
layout: Arc<LineLayout>,
|
||||
style_runs: SmallVec<[StyleRun; 32]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StyleRun {
|
||||
len: u32,
|
||||
color: Hsla,
|
||||
underline: UnderlineStyle,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LineLayout {
|
||||
pub font_size: Pixels,
|
||||
pub width: Pixels,
|
||||
pub ascent: Pixels,
|
||||
pub descent: Pixels,
|
||||
pub runs: Vec<Run>,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Run {
|
||||
pub font_id: FontId,
|
||||
pub glyphs: Vec<Glyph>,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
|
||||
let mut style_runs = SmallVec::new();
|
||||
for (len, style) in runs {
|
||||
style_runs.push(StyleRun {
|
||||
len: *len as u32,
|
||||
color: style.color,
|
||||
underline: style.underline.clone().unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
Self { layout, style_runs }
|
||||
}
|
||||
|
||||
pub fn runs(&self) -> &[Run] {
|
||||
&self.layout.runs
|
||||
}
|
||||
|
||||
pub fn width(&self) -> Pixels {
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> Pixels {
|
||||
self.layout.font_size
|
||||
}
|
||||
|
||||
pub fn x_for_index(&self, index: usize) -> Pixels {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return glyph.position.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return Some(run.font_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.layout.len
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.layout.len == 0
|
||||
}
|
||||
|
||||
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
|
||||
if x >= self.layout.width {
|
||||
None
|
||||
} else {
|
||||
for run in self.layout.runs.iter().rev() {
|
||||
for glyph in run.glyphs.iter().rev() {
|
||||
if glyph.position.x <= x {
|
||||
return Some(glyph.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
visible_bounds: Bounds<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
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 {
|
||||
let max_glyph_width = cx
|
||||
.font_cache()
|
||||
.bounding_box(run.font_id, self.layout.font_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,
|
||||
// });
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// 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,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint_wrapped(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
_visible_bounds: Bounds<Pixels>,
|
||||
line_height: Pixels,
|
||||
boundaries: &[ShapedBoundary],
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
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();
|
||||
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 _glyph_bounds = Bounds {
|
||||
origin: glyph_origin,
|
||||
size: cx
|
||||
.font_cache()
|
||||
.bounding_box(run.font_id, self.layout.font_size),
|
||||
};
|
||||
// todo!()
|
||||
// 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,
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Run {
|
||||
pub fn glyphs(&self) -> &[Glyph] {
|
||||
&self.glyphs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Boundary {
|
||||
pub ix: usize,
|
||||
pub next_indent: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ShapedBoundary {
|
||||
pub run_ix: usize,
|
||||
pub glyph_ix: usize,
|
||||
}
|
||||
|
||||
impl Boundary {
|
||||
fn new(ix: usize, next_indent: u32) -> Self {
|
||||
Self { ix, next_indent }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LineWrapper {
|
||||
font_system: Arc<dyn PlatformFontSystem>,
|
||||
pub(crate) font_id: FontId,
|
||||
pub(crate) font_size: Pixels,
|
||||
cached_ascii_char_widths: [Option<Pixels>; 128],
|
||||
cached_other_char_widths: HashMap<char, Pixels>,
|
||||
}
|
||||
|
||||
impl LineWrapper {
|
||||
pub const MAX_INDENT: u32 = 256;
|
||||
|
||||
pub fn new(
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
font_system: Arc<dyn PlatformFontSystem>,
|
||||
) -> Self {
|
||||
Self {
|
||||
font_system,
|
||||
font_id,
|
||||
font_size,
|
||||
cached_ascii_char_widths: [None; 128],
|
||||
cached_other_char_widths: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_line<'a>(
|
||||
&'a mut self,
|
||||
line: &'a str,
|
||||
wrap_width: Pixels,
|
||||
) -> impl Iterator<Item = Boundary> + 'a {
|
||||
let mut width = px(0.);
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut indent = None;
|
||||
let mut last_candidate_ix = 0;
|
||||
let mut last_candidate_width = px(0.);
|
||||
let mut last_wrap_ix = 0;
|
||||
let mut prev_c = '\0';
|
||||
let mut char_indices = line.char_indices();
|
||||
iter::from_fn(move || {
|
||||
for (ix, c) in char_indices.by_ref() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let char_width = self.width_for_char(c);
|
||||
width += char_width;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
|
||||
{
|
||||
indent = Some(
|
||||
Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
|
||||
);
|
||||
}
|
||||
|
||||
if last_candidate_ix > 0 {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
width -= last_candidate_width;
|
||||
last_candidate_ix = 0;
|
||||
} else {
|
||||
last_wrap_ix = ix;
|
||||
width = char_width;
|
||||
}
|
||||
|
||||
if let Some(indent) = indent {
|
||||
width += self.width_for_char(' ') * indent as f32;
|
||||
}
|
||||
|
||||
return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wrap_shaped_line<'a>(
|
||||
&'a mut self,
|
||||
str: &'a str,
|
||||
line: &'a Line,
|
||||
wrap_width: Pixels,
|
||||
) -> impl Iterator<Item = ShapedBoundary> + 'a {
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut last_candidate_ix = None;
|
||||
let mut last_candidate_x = px(0.);
|
||||
let mut last_wrap_ix = ShapedBoundary {
|
||||
run_ix: 0,
|
||||
glyph_ix: 0,
|
||||
};
|
||||
let mut last_wrap_x = px(0.);
|
||||
let mut prev_c = '\0';
|
||||
let mut glyphs = line
|
||||
.runs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(run_ix, run)| {
|
||||
run.glyphs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(glyph_ix, glyph)| {
|
||||
let character = str[glyph.index..].chars().next().unwrap();
|
||||
(
|
||||
ShapedBoundary { run_ix, glyph_ix },
|
||||
character,
|
||||
glyph.position.x,
|
||||
)
|
||||
})
|
||||
})
|
||||
.peekable();
|
||||
|
||||
iter::from_fn(move || {
|
||||
while let Some((ix, c, x)) = glyphs.next() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = Some(ix);
|
||||
last_candidate_x = x;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
|
||||
let width = next_x - last_wrap_x;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let Some(last_candidate_ix) = last_candidate_ix.take() {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
last_wrap_x = last_candidate_x;
|
||||
} else {
|
||||
last_wrap_ix = ix;
|
||||
last_wrap_x = x;
|
||||
}
|
||||
|
||||
return Some(last_wrap_ix);
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn is_boundary(&self, prev: char, next: char) -> bool {
|
||||
(prev == ' ') && (next != ' ')
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn width_for_char(&mut self, c: char) -> Pixels {
|
||||
if (c as u32) < 128 {
|
||||
if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
|
||||
cached_width
|
||||
} else {
|
||||
let width = self.compute_width_for_char(c);
|
||||
self.cached_ascii_char_widths[c as usize] = Some(width);
|
||||
width
|
||||
}
|
||||
} else {
|
||||
if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
|
||||
*cached_width
|
||||
} else {
|
||||
let width = self.compute_width_for_char(c);
|
||||
self.cached_other_char_widths.insert(c, width);
|
||||
width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_width_for_char(&self, c: char) -> Pixels {
|
||||
self.font_system
|
||||
.layout_line(
|
||||
&c.to_string(),
|
||||
self.font_size,
|
||||
&[(
|
||||
1,
|
||||
RunStyle {
|
||||
font_id: self.font_id,
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
},
|
||||
)],
|
||||
)
|
||||
.width
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{AppContext, FontWeight};
|
||||
|
||||
#[test]
|
||||
fn test_wrap_line() {
|
||||
let cx = AppContext::test();
|
||||
|
||||
let font_cache = cx.font_cache().clone();
|
||||
let font_system = cx.platform().font_system();
|
||||
let family = font_cache
|
||||
.load_family(&["Courier"], &Default::default())
|
||||
.unwrap();
|
||||
let font_id = font_cache
|
||||
.select_font(family, Default::default(), Default::default())
|
||||
.unwrap();
|
||||
|
||||
let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line("aa bbb cccc ddddd eeee", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(12, 0),
|
||||
Boundary::new(18, 0)
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(4, 0),
|
||||
Boundary::new(11, 0),
|
||||
Boundary::new(18, 0)
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" aaaaaaa", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 5),
|
||||
Boundary::new(9, 5),
|
||||
Boundary::new(11, 5),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" ", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(14, 0),
|
||||
Boundary::new(21, 0)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" aaaaaaaaaaaaaa", px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(14, 3),
|
||||
Boundary::new(18, 3),
|
||||
Boundary::new(22, 3),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// todo! repeat this test
|
||||
#[test]
|
||||
fn test_wrap_shaped_line() {
|
||||
let cx = AppContext::test();
|
||||
let font_cache = cx.font_cache().clone();
|
||||
let font_system = cx.platform().font_system();
|
||||
let text_layout_cache = TextLayoutCache::new(font_system.clone());
|
||||
|
||||
let family = font_cache
|
||||
.load_family(&["Helvetica"], &Default::default())
|
||||
.unwrap();
|
||||
let font_id = font_cache
|
||||
.select_font(family, Default::default(), Default::default())
|
||||
.unwrap();
|
||||
let normal = RunStyle {
|
||||
font_id,
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
let bold = RunStyle {
|
||||
font_id: font_cache
|
||||
.select_font(family, FontWeight::BOLD, Default::default())
|
||||
.unwrap(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
|
||||
let text = "aa bbb cccc ddddd eeee";
|
||||
let line = text_layout_cache.layout_str(
|
||||
text,
|
||||
px(16.),
|
||||
&[
|
||||
(4, normal.clone()),
|
||||
(5, bold.clone()),
|
||||
(6, normal.clone()),
|
||||
(1, bold),
|
||||
(7, normal),
|
||||
],
|
||||
);
|
||||
|
||||
let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_shaped_line(text, &line, px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
ShapedBoundary {
|
||||
run_ix: 1,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 2,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 4,
|
||||
glyph_ix: 2
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
use super::{
|
||||
px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference, Style,
|
||||
TaffyLayoutEngine,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
pub struct AnyWindow {}
|
||||
|
||||
pub struct Window {
|
||||
id: WindowId,
|
||||
rem_size: Pixels,
|
||||
layout_engine: TaffyLayoutEngine,
|
||||
pub(crate) root_view: Option<Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(id: WindowId) -> Window {
|
||||
Window {
|
||||
id,
|
||||
layout_engine: TaffyLayoutEngine::new(),
|
||||
rem_size: px(16.),
|
||||
root_view: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct WindowContext<'a, 'b> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
app: Reference<'a, AppContext>,
|
||||
window: Reference<'b, Window>,
|
||||
}
|
||||
|
||||
impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self {
|
||||
Self {
|
||||
app: Reference::Mutable(app),
|
||||
window: Reference::Mutable(window),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn immutable(app: &'a AppContext, window: &'w Window) -> Self {
|
||||
Self {
|
||||
app: Reference::Immutable(app),
|
||||
window: Reference::Immutable(window),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
children: impl IntoIterator<Item = LayoutId>,
|
||||
) -> Result<LayoutId> {
|
||||
self.app.layout_id_buffer.clear();
|
||||
self.app.layout_id_buffer.extend(children.into_iter());
|
||||
let rem_size = self.rem_size();
|
||||
|
||||
self.window
|
||||
.layout_engine
|
||||
.request_layout(style, rem_size, &self.app.layout_id_buffer)
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
|
||||
Ok(self
|
||||
.window
|
||||
.layout_engine
|
||||
.layout(layout_id)
|
||||
.map(Into::into)?)
|
||||
}
|
||||
|
||||
pub fn rem_size(&self) -> Pixels {
|
||||
self.window.rem_size
|
||||
}
|
||||
|
||||
fn update_window<R>(
|
||||
&mut self,
|
||||
window_id: WindowId,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
if window_id == self.window.id {
|
||||
Ok(update(self))
|
||||
} else {
|
||||
self.app.update_window(window_id, update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for WindowContext<'_, '_> {
|
||||
type EntityContext<'a, 'w, T: 'static> = ViewContext<'a, 'w, T>;
|
||||
|
||||
fn entity<T: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
|
||||
) -> Handle<T> {
|
||||
let id = self.entities.insert(None);
|
||||
let entity = Box::new(build_entity(&mut ViewContext::mutable(
|
||||
&mut *self.app,
|
||||
&mut self.window,
|
||||
id,
|
||||
)));
|
||||
self.entities.get_mut(id).unwrap().replace(entity);
|
||||
|
||||
Handle {
|
||||
id,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
|
||||
) -> R {
|
||||
let mut entity = self
|
||||
.app
|
||||
.entities
|
||||
.get_mut(handle.id)
|
||||
.unwrap()
|
||||
.take()
|
||||
.unwrap()
|
||||
.downcast::<T>()
|
||||
.unwrap();
|
||||
|
||||
let result = update(
|
||||
&mut *entity,
|
||||
&mut ViewContext::mutable(&mut *self.app, &mut *self.window, handle.id),
|
||||
);
|
||||
|
||||
self.app
|
||||
.entities
|
||||
.get_mut(handle.id)
|
||||
.unwrap()
|
||||
.replace(entity);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct ViewContext<'a, 'w, T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
window_cx: WindowContext<'a, 'w>,
|
||||
entity_type: PhantomData<T>,
|
||||
entity_id: EntityId,
|
||||
}
|
||||
|
||||
impl<'a, 'w, T: 'static> ViewContext<'a, 'w, T> {
|
||||
// fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
|
||||
|
||||
// self.window_cx.update_entity(handle, update)
|
||||
|
||||
// let mut entity = self.window_cx.app.entities.remove(&self.entity_id).unwrap();
|
||||
// let result = update(entity.downcast_mut::<T>().unwrap(), self);
|
||||
// self.window_cx
|
||||
// .app
|
||||
// .entities
|
||||
// .insert(self.entity_id, Box::new(entity));
|
||||
// result
|
||||
// }
|
||||
|
||||
fn mutable(app: &'a mut AppContext, window: &'w mut Window, entity_id: EntityId) -> Self {
|
||||
Self {
|
||||
window_cx: WindowContext::mutable(app, window),
|
||||
entity_id,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn immutable(app: &'a AppContext, window: &'w Window, entity_id: EntityId) -> Self {
|
||||
Self {
|
||||
window_cx: WindowContext::immutable(app, window),
|
||||
entity_id,
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'w, T: 'static> Context for ViewContext<'a, 'w, T> {
|
||||
type EntityContext<'b, 'c, U: 'static> = ViewContext<'b, 'c, U>;
|
||||
|
||||
fn entity<T2: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T2>) -> T2,
|
||||
) -> Handle<T2> {
|
||||
self.window_cx.entity(build_entity)
|
||||
}
|
||||
|
||||
fn update_entity<U: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Handle<U>,
|
||||
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
|
||||
) -> R {
|
||||
self.window_cx.update_entity(handle, update)
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
|
||||
slotmap::new_key_type! { pub struct WindowId; }
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct WindowHandle<S> {
|
||||
id: WindowId,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> Copy for WindowHandle<S> {}
|
||||
|
||||
impl<S> Clone for WindowHandle<S> {
|
||||
fn clone(&self) -> Self {
|
||||
WindowHandle {
|
||||
id: self.id,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> WindowHandle<S> {
|
||||
pub fn new(id: WindowId) -> Self {
|
||||
WindowHandle {
|
||||
id,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Into<AnyWindowHandle> for WindowHandle<S> {
|
||||
fn into(self) -> AnyWindowHandle {
|
||||
AnyWindowHandle {
|
||||
id: self.id,
|
||||
state_type: TypeId::of::<S>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyWindowHandle {
|
||||
id: WindowId,
|
||||
state_type: TypeId,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Layout {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<Pixels>,
|
||||
}
|
||||
Reference in New Issue
Block a user