Checkpoint

This commit is contained in:
Antonio Scandurra
2023-10-21 16:01:47 +02:00
parent f3979a9f28
commit e4fe9538d7
107 changed files with 81 additions and 81 deletions
+408
View File
@@ -0,0 +1,408 @@
use crate::SharedString;
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet};
use std::any::Any;
pub trait Action: Any + Send + Sync {
fn partial_eq(&self, action: &dyn Action) -> bool;
fn boxed_clone(&self) -> Box<dyn Action>;
fn as_any(&self) -> &dyn Any;
}
impl<T> Action for T
where
T: Any + PartialEq + Clone + Send + Sync,
{
fn partial_eq(&self, action: &dyn Action) -> bool {
action
.as_any()
.downcast_ref::<Self>()
.map_or(false, |a| self == a)
}
fn boxed_clone(&self) -> Box<dyn Action> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DispatchContext {
set: HashSet<SharedString>,
map: HashMap<SharedString, SharedString>,
}
impl<'a> TryFrom<&'a str> for DispatchContext {
type Error = anyhow::Error;
fn try_from(value: &'a str) -> Result<Self> {
Self::parse(value)
}
}
impl DispatchContext {
pub fn parse(source: &str) -> Result<Self> {
let mut context = Self::default();
let source = skip_whitespace(source);
Self::parse_expr(&source, &mut context)?;
Ok(context)
}
fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
if source.is_empty() {
return Ok(());
}
let key = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[key.len()..]);
if let Some(suffix) = source.strip_prefix('=') {
source = skip_whitespace(suffix);
let value = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[value.len()..]);
context.set(key, value);
} else {
context.insert(key);
}
Self::parse_expr(source, context)
}
pub fn is_empty(&self) -> bool {
self.set.is_empty() && self.map.is_empty()
}
pub fn clear(&mut self) {
self.set.clear();
self.map.clear();
}
pub fn extend(&mut self, other: &Self) {
for v in &other.set {
self.set.insert(v.clone());
}
for (k, v) in &other.map {
self.map.insert(k.clone(), v.clone());
}
}
pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
self.set.insert(identifier.into());
}
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
self.map.insert(key.into(), value.into());
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum DispatchContextPredicate {
Identifier(SharedString),
Equal(SharedString, SharedString),
NotEqual(SharedString, SharedString),
Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
Not(Box<DispatchContextPredicate>),
And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
}
impl DispatchContextPredicate {
pub fn parse(source: &str) -> Result<Self> {
let source = skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}"))
} else {
Ok(predicate)
}
}
pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
let Some(context) = contexts.last() else {
return false;
};
match self {
Self::Identifier(name) => context.set.contains(&name),
Self::Equal(left, right) => context
.map
.get(&left)
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.map
.get(&left)
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
Self::Child(parent, child) => {
parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
}
Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
}
}
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
type Op = fn(
DispatchContextPredicate,
DispatchContextPredicate,
) -> Result<DispatchContextPredicate>;
let (mut predicate, rest) = Self::parse_primary(source)?;
source = rest;
'parse: loop {
for (operator, precedence, constructor) in [
(">", PRECEDENCE_CHILD, Self::new_child as Op),
("&&", PRECEDENCE_AND, Self::new_and as Op),
("||", PRECEDENCE_OR, Self::new_or as Op),
("==", PRECEDENCE_EQ, Self::new_eq as Op),
("!=", PRECEDENCE_EQ, Self::new_neq as Op),
] {
if source.starts_with(operator) && precedence >= min_precedence {
source = skip_whitespace(&source[operator.len()..]);
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
predicate = constructor(predicate, right)?;
source = rest;
continue 'parse;
}
}
break;
}
Ok((predicate, source))
}
fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
let next = source
.chars()
.next()
.ok_or_else(|| anyhow!("unexpected eof"))?;
match next {
'(' => {
source = skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
source = skip_whitespace(&rest[1..]);
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
let source = skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
}
_ if is_identifier_char(next) => {
let len = source
.find(|c: char| !is_identifier_char(c))
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
source = skip_whitespace(rest);
Ok((
DispatchContextPredicate::Identifier(identifier.to_string().into()),
source,
))
}
_ => Err(anyhow!("unexpected character {next:?}")),
}
}
fn new_or(self, other: Self) -> Result<Self> {
Ok(Self::Or(Box::new(self), Box::new(other)))
}
fn new_and(self, other: Self) -> Result<Self> {
Ok(Self::And(Box::new(self), Box::new(other)))
}
fn new_child(self, other: Self) -> Result<Self> {
Ok(Self::Child(Box::new(self), Box::new(other)))
}
fn new_eq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::Equal(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
fn new_neq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::NotEqual(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
}
const PRECEDENCE_CHILD: u32 = 1;
const PRECEDENCE_OR: u32 = 2;
const PRECEDENCE_AND: u32 = 3;
const PRECEDENCE_EQ: u32 = 4;
const PRECEDENCE_NOT: u32 = 5;
fn is_identifier_char(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '-'
}
fn skip_whitespace(source: &str) -> &str {
let len = source
.find(|c: char| !c.is_whitespace())
.unwrap_or(source.len());
&source[len..]
}
#[cfg(test)]
mod tests {
use super::*;
use DispatchContextPredicate::*;
#[test]
fn test_parse_context() {
let mut expected = DispatchContext::default();
expected.set("foo", "bar");
expected.insert("baz");
assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
assert_eq!(
DispatchContext::parse(" baz foo = bar baz").unwrap(),
expected
);
assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
}
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
DispatchContextPredicate::parse("abc12").unwrap(),
Identifier("abc12".into())
);
assert_eq!(
DispatchContextPredicate::parse("_1a").unwrap(),
Identifier("_1a".into())
);
}
#[test]
fn test_parse_negations() {
assert_eq!(
DispatchContextPredicate::parse("!abc").unwrap(),
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
DispatchContextPredicate::parse(" ! ! abc").unwrap(),
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
#[test]
fn test_parse_equality_operators() {
assert_eq!(
DispatchContextPredicate::parse("a == b").unwrap(),
Equal("a".into(), "b".into())
);
assert_eq!(
DispatchContextPredicate::parse("c!=d").unwrap(),
NotEqual("c".into(), "d".into())
);
assert_eq!(
DispatchContextPredicate::parse("c == !d")
.unwrap_err()
.to_string(),
"operands must be identifiers"
);
}
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
DispatchContextPredicate::parse("a || b").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
DispatchContextPredicate::parse("a || !b && c").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(And(
Box::new(Not(Box::new(Identifier("b".into())))),
Box::new(Identifier("c".into()))
))
)
);
assert_eq!(
DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
Or(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(And(
Box::new(Identifier("c".into())),
Box::new(Identifier("d".into()))
))
)
);
assert_eq!(
DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
Or(
Box::new(And(
Box::new(Equal("a".into(), "b".into())),
Box::new(Identifier("c".into()))
)),
Box::new(And(
Box::new(Equal("d".into(), "e".into())),
Box::new(Identifier("f".into()))
))
)
);
assert_eq!(
DispatchContextPredicate::parse("a && b && c && d").unwrap(),
And(
Box::new(And(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(Identifier("c".into())),
)),
Box::new(Identifier("d".into()))
),
);
}
#[test]
fn test_parse_parenthesized_expressions() {
assert_eq!(
DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
And(
Box::new(Identifier("a".into())),
Box::new(Or(
Box::new(Equal("b".into(), "c".into())),
Box::new(NotEqual("d".into(), "e".into())),
)),
),
);
assert_eq!(
DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
)
);
}
}
+527
View File
@@ -0,0 +1,527 @@
mod async_context;
mod entity_map;
mod model_context;
pub use async_context::*;
pub use entity_map::*;
pub use model_context::*;
use refineable::Refineable;
use smallvec::SmallVec;
use crate::{
current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor,
FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
Platform, SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View,
Window, WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
use futures::Future;
use parking_lot::{Mutex, RwLock};
use slotmap::SlotMap;
use std::{
any::{type_name, Any, TypeId},
mem,
sync::{atomic::Ordering::SeqCst, Arc, Weak},
};
use util::http::{self, HttpClient};
#[derive(Clone)]
pub struct App(Arc<Mutex<AppContext>>);
impl App {
pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
let http_client = http::client();
Self::new(current_platform(), asset_source, http_client)
}
#[cfg(any(test, feature = "test"))]
pub fn test() -> Self {
let platform = Arc::new(super::TestPlatform::new());
let asset_source = Arc::new(());
let http_client = util::http::FakeHttpClient::with_404_response();
Self::new(platform, asset_source, http_client)
}
fn new(
platform: Arc<dyn Platform>,
asset_source: Arc<dyn AssetSource>,
http_client: Arc<dyn HttpClient>,
) -> Self {
let executor = platform.executor();
let entities = EntityMap::new();
let unit_entity = entities.insert(entities.reserve(), ());
Self(Arc::new_cyclic(|this| {
Mutex::new(AppContext {
this: this.clone(),
text_system: Arc::new(TextSystem::new(platform.text_system())),
pending_updates: 0,
flushing_effects: false,
next_frame_callbacks: Default::default(),
platform: MainThreadOnly::new(platform, executor.clone()),
executor,
svg_renderer: SvgRenderer::new(asset_source),
image_cache: ImageCache::new(http_client),
text_style_stack: Vec::new(),
global_stacks_by_type: HashMap::default(),
unit_entity,
entities,
windows: SlotMap::with_key(),
keymap: Arc::new(RwLock::new(Keymap::default())),
pending_notifications: Default::default(),
pending_effects: Default::default(),
observers: SubscriberSet::new(),
event_handlers: SubscriberSet::new(),
release_handlers: SubscriberSet::new(),
layout_id_buffer: Default::default(),
})
}))
}
pub fn run<F>(self, on_finish_launching: F)
where
F: 'static + FnOnce(&mut MainThread<AppContext>),
{
let this = self.clone();
let platform = self.0.lock().platform.clone();
platform.borrow_on_main_thread().run(Box::new(move || {
let cx = &mut *this.0.lock();
let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
on_finish_launching(cx);
}));
}
}
type Handler = Box<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>;
type EventHandler = Box<dyn Fn(&dyn Any, &mut AppContext) -> bool + Send + Sync + 'static>;
type ReleaseHandler = Box<dyn Fn(&mut dyn Any, &mut AppContext) + Send + Sync + 'static>;
type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
pub struct AppContext {
this: Weak<Mutex<AppContext>>,
pub(crate) platform: MainThreadOnly<dyn Platform>,
text_system: Arc<TextSystem>,
flushing_effects: bool,
pending_updates: usize,
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
pub(crate) executor: Executor,
pub(crate) svg_renderer: SvgRenderer,
pub(crate) image_cache: ImageCache,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) global_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
pub(crate) unit_entity: Handle<()>,
pub(crate) entities: EntityMap,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
pub(crate) keymap: Arc<RwLock<Keymap>>,
pub(crate) pending_notifications: HashSet<EntityId>,
pending_effects: VecDeque<Effect>,
pub(crate) observers: SubscriberSet<EntityId, Handler>,
pub(crate) event_handlers: SubscriberSet<EntityId, EventHandler>,
pub(crate) release_handlers: SubscriberSet<EntityId, ReleaseHandler>,
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
}
impl AppContext {
pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
self.pending_updates += 1;
let result = update(self);
if !self.flushing_effects && self.pending_updates == 1 {
self.flushing_effects = true;
self.flush_effects();
self.flushing_effects = false;
}
self.pending_updates -= 1;
result
}
pub(crate) fn update_window<R>(
&mut self,
id: WindowId,
update: impl FnOnce(&mut WindowContext) -> R,
) -> Result<R> {
self.update(|cx| {
let mut window = cx
.windows
.get_mut(id)
.ok_or_else(|| anyhow!("window not found"))?
.take()
.unwrap();
let result = update(&mut WindowContext::mutable(cx, &mut window));
cx.windows
.get_mut(id)
.ok_or_else(|| anyhow!("window not found"))?
.replace(window);
Ok(result)
})
}
pub(crate) fn push_effect(&mut self, effect: Effect) {
match &effect {
Effect::Notify { emitter } => {
if self.pending_notifications.insert(*emitter) {
self.pending_effects.push_back(effect);
}
}
Effect::Emit { .. } => self.pending_effects.push_back(effect),
Effect::FocusChanged { .. } => self.pending_effects.push_back(effect),
Effect::Refresh => self.pending_effects.push_back(effect),
}
}
fn flush_effects(&mut self) {
loop {
self.release_dropped_entities();
self.release_dropped_focus_handles();
if let Some(effect) = self.pending_effects.pop_front() {
match effect {
Effect::Notify { emitter } => self.apply_notify_effect(emitter),
Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event),
Effect::FocusChanged { window_id, focused } => {
self.apply_focus_changed(window_id, focused)
}
Effect::Refresh => {
self.apply_refresh();
}
}
} else {
break;
}
}
let dirty_window_ids = self
.windows
.iter()
.filter_map(|(window_id, window)| {
let window = window.as_ref().unwrap();
if window.dirty {
Some(window_id)
} else {
None
}
})
.collect::<SmallVec<[_; 8]>>();
for dirty_window_id in dirty_window_ids {
self.update_window(dirty_window_id, |cx| cx.draw()).unwrap();
}
}
fn release_dropped_entities(&mut self) {
loop {
let dropped = self.entities.take_dropped();
if dropped.is_empty() {
break;
}
for (entity_id, mut entity) in dropped {
self.observers.remove(&entity_id);
self.event_handlers.remove(&entity_id);
for release_callback in self.release_handlers.remove(&entity_id) {
release_callback(&mut entity, self);
}
}
}
}
fn release_dropped_focus_handles(&mut self) {
let window_ids = self.windows.keys().collect::<SmallVec<[_; 8]>>();
for window_id in window_ids {
self.update_window(window_id, |cx| {
let mut blur_window = false;
let focus = cx.window.focus;
cx.window.focus_handles.write().retain(|handle_id, count| {
if count.load(SeqCst) == 0 {
if focus == Some(handle_id) {
blur_window = true;
}
false
} else {
true
}
});
if blur_window {
cx.blur();
}
})
.unwrap();
}
}
fn apply_notify_effect(&mut self, emitter: EntityId) {
self.pending_notifications.remove(&emitter);
self.observers
.clone()
.retain(&emitter, |handler| handler(self));
}
fn apply_emit_effect(&mut self, emitter: EntityId, event: Box<dyn Any>) {
self.event_handlers
.clone()
.retain(&emitter, |handler| handler(&event, self));
}
fn apply_focus_changed(&mut self, window_id: WindowId, focused: Option<FocusId>) {
self.update_window(window_id, |cx| {
if cx.window.focus == focused {
let mut listeners = mem::take(&mut cx.window.focus_listeners);
let focused =
focused.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
let blurred = cx
.window
.last_blur
.take()
.unwrap()
.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
if focused.is_some() || blurred.is_some() {
let event = FocusEvent { focused, blurred };
for listener in &listeners {
listener(&event, cx);
}
}
listeners.extend(cx.window.focus_listeners.drain(..));
cx.window.focus_listeners = listeners;
}
})
.ok();
}
pub fn apply_refresh(&mut self) {
for window in self.windows.values_mut() {
if let Some(window) = window.as_mut() {
window.dirty = true;
}
}
}
pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext(unsafe { mem::transmute(self.this.clone()) })
}
pub fn executor(&self) -> &Executor {
&self.executor
}
pub fn run_on_main<R>(
&mut self,
f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
) -> Task<R>
where
R: Send + 'static,
{
if self.executor.is_main_thread() {
Task::ready(f(unsafe {
mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(self)
}))
} else {
let this = self.this.upgrade().unwrap();
self.executor.run_on_main(move || {
let cx = &mut *this.lock();
cx.update(|cx| f(unsafe { mem::transmute::<&mut Self, &mut MainThread<Self>>(cx) }))
})
}
}
pub fn spawn_on_main<F, R>(
&self,
f: impl FnOnce(&mut MainThread<AppContext>) -> F + Send + 'static,
) -> Task<R>
where
F: Future<Output = R> + 'static,
R: Send + 'static,
{
let this = self.this.upgrade().unwrap();
self.executor.spawn_on_main(move || {
let cx = &mut *this.lock();
cx.update(|cx| {
f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
})
})
}
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
where
Fut: Future<Output = R> + Send + 'static,
R: Send + 'static,
{
let cx = self.to_async();
self.executor.spawn(async move {
let future = f(cx);
future.await
})
}
pub fn text_system(&self) -> &Arc<TextSystem> {
&self.text_system
}
pub fn text_style(&self) -> TextStyle {
let mut style = TextStyle::default();
for refinement in &self.text_style_stack {
style.refine(refinement);
}
style
}
pub fn global<G: 'static>(&self) -> &G {
self.global_stacks_by_type
.get(&TypeId::of::<G>())
.and_then(|stack| stack.last())
.and_then(|any_state| any_state.downcast_ref::<G>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
.unwrap()
}
pub fn global_mut<G: 'static>(&mut self) -> &mut G {
self.global_stacks_by_type
.get_mut(&TypeId::of::<G>())
.and_then(|stack| stack.last_mut())
.and_then(|any_state| any_state.downcast_mut::<G>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
.unwrap()
}
pub fn default_global<G: 'static + Default + Sync + Send>(&mut self) -> &mut G {
let stack = self
.global_stacks_by_type
.entry(TypeId::of::<G>())
.or_default();
if stack.is_empty() {
stack.push(Box::new(G::default()));
}
stack.last_mut().unwrap().downcast_mut::<G>().unwrap()
}
pub(crate) fn push_global<T: Send + Sync + 'static>(&mut self, state: T) {
self.global_stacks_by_type
.entry(TypeId::of::<T>())
.or_default()
.push(Box::new(state));
}
pub(crate) fn pop_global<T: 'static>(&mut self) {
self.global_stacks_by_type
.get_mut(&TypeId::of::<T>())
.and_then(|stack| stack.pop())
.expect("state stack underflow");
}
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
self.text_style_stack.push(text_style);
}
pub(crate) fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
self.keymap.write().add_bindings(bindings);
self.push_effect(Effect::Refresh);
}
}
impl Context for AppContext {
type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>;
type Result<T> = T;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Handle<T> {
self.update(|cx| {
let slot = cx.entities.reserve();
let entity = build_entity(&mut ModelContext::mutable(cx, slot.id));
cx.entities.insert(slot, entity)
})
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> R {
self.update(|cx| {
let mut entity = cx.entities.lease(handle);
let result = update(&mut entity, &mut ModelContext::mutable(cx, handle.id));
cx.entities.end_lease(entity);
result
})
}
}
impl MainThread<AppContext> {
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
self.0.update(|cx| {
update(unsafe {
std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
})
})
}
pub(crate) fn update_window<R>(
&mut self,
id: WindowId,
update: impl FnOnce(&mut MainThread<WindowContext>) -> R,
) -> Result<R> {
self.0.update_window(id, |cx| {
update(unsafe {
std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
})
})
}
pub(crate) fn platform(&self) -> &dyn Platform {
self.platform.borrow_on_main_thread()
}
pub fn activate(&mut self, ignoring_other_apps: bool) {
self.platform().activate(ignoring_other_apps);
}
pub fn open_window<S: 'static + Send + Sync>(
&mut self,
options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut WindowContext) -> View<S> + Send + 'static,
) -> WindowHandle<S> {
self.update(|cx| {
let id = cx.windows.insert(None);
let handle = WindowHandle::new(id);
let mut window = Window::new(handle.into(), options, cx);
let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
window.root_view.replace(root_view.into_any());
cx.windows.get_mut(id).unwrap().replace(window);
handle
})
}
}
pub(crate) enum Effect {
Notify {
emitter: EntityId,
},
Emit {
emitter: EntityId,
event: Box<dyn Any + Send + Sync + 'static>,
},
FocusChanged {
window_id: WindowId,
focused: Option<FocusId>,
},
Refresh,
}
#[cfg(test)]
mod tests {
use super::AppContext;
#[test]
fn test_app_context_send_sync() {
// This will not compile if `AppContext` does not implement `Send`
fn assert_send<T: Send>() {}
assert_send::<AppContext>();
}
}
+98
View File
@@ -0,0 +1,98 @@
use crate::{
AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, ViewContext, WindowContext,
};
use anyhow::anyhow;
use parking_lot::Mutex;
use std::sync::Weak;
#[derive(Clone)]
pub struct AsyncAppContext(pub(crate) Weak<Mutex<AppContext>>);
impl Context for AsyncAppContext {
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ModelContext<'a, T>;
type Result<T> = Result<T>;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Result<Handle<T>> {
let app = self
.0
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.lock(); // Does not compile without this variable.
Ok(lock.entity(build_entity))
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> Result<R> {
let app = self
.0
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.lock(); // Does not compile without this variable.
Ok(lock.update_entity(handle, update))
}
}
impl AsyncAppContext {
pub fn update_window<R>(
&self,
handle: AnyWindowHandle,
update: impl FnOnce(&mut WindowContext) -> R,
) -> Result<R> {
let app = self
.0
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app_context = app.lock();
app_context.update_window(handle.id, update)
}
}
#[derive(Clone)]
pub struct AsyncWindowContext {
app: AsyncAppContext,
window: AnyWindowHandle,
}
impl AsyncWindowContext {
pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
Self { app, window }
}
pub fn update<R>(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
self.app.update_window(self.window, update)
}
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
self.app
.update_window(self.window, |cx| cx.on_next_frame(f))
.ok();
}
}
impl Context for AsyncWindowContext {
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
type Result<T> = Result<T>;
fn entity<R: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, R>) -> R,
) -> Result<Handle<R>> {
self.app
.update_window(self.window, |cx| cx.entity(build_entity))
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> Result<R> {
self.app
.update_window(self.window, |cx| cx.update_entity(handle, update))
}
}
+248
View File
@@ -0,0 +1,248 @@
use crate::Context;
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use slotmap::{SecondaryMap, SlotMap};
use std::{
any::Any,
marker::PhantomData,
mem,
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak,
},
};
slotmap::new_key_type! { pub struct EntityId; }
pub(crate) struct EntityMap(Arc<RwLock<EntityMapState>>);
struct EntityMapState {
ref_counts: SlotMap<EntityId, AtomicUsize>,
entities: SecondaryMap<EntityId, Box<dyn Any + Send + Sync>>,
dropped_entities: Vec<(EntityId, Box<dyn Any + Send + Sync>)>,
}
impl EntityMap {
pub fn new() -> Self {
Self(Arc::new(RwLock::new(EntityMapState {
ref_counts: SlotMap::with_key(),
entities: SecondaryMap::new(),
dropped_entities: Vec::new(),
})))
}
/// Reserve a slot for an entity, which you can subsequently use with `insert`.
pub fn reserve<T: 'static + Send + Sync>(&self) -> Slot<T> {
let id = self.0.write().ref_counts.insert(1.into());
Slot(Handle::new(id, Arc::downgrade(&self.0)))
}
/// Insert an entity into a slot obtained by calling `reserve`.
pub fn insert<T: 'static + Any + Send + Sync>(&self, slot: Slot<T>, entity: T) -> Handle<T> {
let handle = slot.0;
self.0.write().entities.insert(handle.id, Box::new(entity));
handle
}
/// Move an entity to the stack.
pub fn lease<T: 'static + Send + Sync>(&self, handle: &Handle<T>) -> Lease<T> {
let id = handle.id;
let entity = Some(
self.0
.write()
.entities
.remove(id)
.expect("Circular entity lease. Is the entity already being updated?")
.downcast::<T>()
.unwrap(),
);
Lease { id, entity }
}
/// Return an entity after moving it to the stack.
pub fn end_lease<T: 'static + Send + Sync>(&mut self, mut lease: Lease<T>) {
self.0
.write()
.entities
.insert(lease.id, lease.entity.take().unwrap());
}
pub fn weak_handle<T: 'static + Send + Sync>(&self, id: EntityId) -> WeakHandle<T> {
WeakHandle {
id,
entity_type: PhantomData,
entity_map: Arc::downgrade(&self.0),
}
}
pub fn take_dropped(&self) -> Vec<(EntityId, Box<dyn Any + Send + Sync>)> {
mem::take(&mut self.0.write().dropped_entities)
}
}
pub struct Lease<T> {
entity: Option<Box<T>>,
pub id: EntityId,
}
impl<T> core::ops::Deref for Lease<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.entity.as_ref().unwrap()
}
}
impl<T> core::ops::DerefMut for Lease<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.entity.as_mut().unwrap()
}
}
impl<T> Drop for Lease<T> {
fn drop(&mut self) {
if self.entity.is_some() {
// We don't panic here, because other panics can cause us to drop the lease without ending it cleanly.
log::error!("Leases must be ended with EntityMap::end_lease")
}
}
}
#[derive(Deref, DerefMut)]
pub struct Slot<T: Send + Sync + 'static>(Handle<T>);
pub struct Handle<T: Send + Sync> {
pub(crate) id: EntityId,
entity_type: PhantomData<T>,
entity_map: Weak<RwLock<EntityMapState>>,
}
impl<T: 'static + Send + Sync> Handle<T> {
fn new(id: EntityId, entity_map: Weak<RwLock<EntityMapState>>) -> Self {
Self {
id,
entity_type: PhantomData,
entity_map,
}
}
pub fn downgrade(&self) -> WeakHandle<T> {
WeakHandle {
id: self.id,
entity_type: self.entity_type,
entity_map: self.entity_map.clone(),
}
}
/// Update the entity referenced by this handle with the given function.
///
/// The update function receives a context appropriate for its environment.
/// When updating in an `AppContext`, it receives a `ModelContext`.
/// When updating an a `WindowContext`, it receives a `ViewContext`.
pub fn update<C: Context, R>(
&self,
cx: &mut C,
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
) -> C::Result<R> {
cx.update_entity(self, update)
}
}
impl<T: Send + Sync> Clone for Handle<T> {
fn clone(&self) -> Self {
if let Some(entity_map) = self.entity_map.upgrade() {
let entity_map = entity_map.read();
let count = entity_map
.ref_counts
.get(self.id)
.expect("detected over-release of a handle");
let prev_count = count.fetch_add(1, SeqCst);
assert_ne!(prev_count, 0, "Detected over-release of a handle.");
}
Self {
id: self.id,
entity_type: PhantomData,
entity_map: self.entity_map.clone(),
}
}
}
impl<T: Send + Sync> Drop for Handle<T> {
fn drop(&mut self) {
if let Some(entity_map) = self.entity_map.upgrade() {
let entity_map = entity_map.upgradable_read();
let count = entity_map
.ref_counts
.get(self.id)
.expect("Detected over-release of a handle.");
let prev_count = count.fetch_sub(1, SeqCst);
assert_ne!(prev_count, 0, "Detected over-release of a handle.");
if prev_count == 1 {
// We were the last reference to this entity, so we can remove it.
let mut entity_map = RwLockUpgradableReadGuard::upgrade(entity_map);
let entity = entity_map
.entities
.remove(self.id)
.expect("entity was removed twice");
entity_map.ref_counts.remove(self.id);
entity_map.dropped_entities.push((self.id, entity));
}
}
}
}
pub struct WeakHandle<T> {
pub(crate) id: EntityId,
entity_type: PhantomData<T>,
entity_map: Weak<RwLock<EntityMapState>>,
}
impl<T: 'static + Send + Sync> Clone for WeakHandle<T> {
fn clone(&self) -> Self {
Self {
id: self.id,
entity_type: self.entity_type,
entity_map: self.entity_map.clone(),
}
}
}
impl<T: Send + Sync + 'static> WeakHandle<T> {
pub fn upgrade(&self, _: &impl Context) -> Option<Handle<T>> {
let entity_map = &self.entity_map.upgrade()?;
entity_map
.read()
.ref_counts
.get(self.id)?
.fetch_add(1, SeqCst);
Some(Handle {
id: self.id,
entity_type: self.entity_type,
entity_map: self.entity_map.clone(),
})
}
/// Update the entity referenced by this handle with the given function if
/// the referenced entity still exists. Returns an error if the entity has
/// been released.
///
/// The update function receives a context appropriate for its environment.
/// When updating in an `AppContext`, it receives a `ModelContext`.
/// When updating an a `WindowContext`, it receives a `ViewContext`.
pub fn update<C: Context, R>(
&self,
cx: &mut C,
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
) -> Result<R>
where
Result<C::Result<R>>: crate::Flatten<R>,
{
crate::Flatten::flatten(
self.upgrade(cx)
.ok_or_else(|| anyhow!("entity release"))
.map(|this| cx.update_entity(&this, update)),
)
}
}
+152
View File
@@ -0,0 +1,152 @@
use crate::{
AppContext, Context, Effect, EntityId, EventEmitter, Handle, Reference, Subscription,
WeakHandle,
};
use std::marker::PhantomData;
pub struct ModelContext<'a, T> {
app: Reference<'a, AppContext>,
entity_type: PhantomData<T>,
entity_id: EntityId,
}
impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self {
Self {
app: Reference::Mutable(app),
entity_type: PhantomData,
entity_id,
}
}
// todo!
// fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
// let mut entity = self
// .app
// .entities
// .get_mut(self.entity_id)
// .unwrap()
// .take()
// .unwrap();
// let result = update(entity.downcast_mut::<T>().unwrap(), self);
// self.app
// .entities
// .get_mut(self.entity_id)
// .unwrap()
// .replace(entity);
// result
// }
pub fn handle(&self) -> WeakHandle<T> {
self.app.entities.weak_handle(self.entity_id)
}
pub fn observe<E: Send + Sync + 'static>(
&mut self,
handle: &Handle<E>,
on_notify: impl Fn(&mut T, Handle<E>, &mut ModelContext<'_, T>) + Send + Sync + 'static,
) -> Subscription {
let this = self.handle();
let handle = handle.downgrade();
self.app.observers.insert(
handle.id,
Box::new(move |cx| {
if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
this.update(cx, |this, cx| on_notify(this, handle, cx));
true
} else {
false
}
}),
)
}
pub fn subscribe<E: EventEmitter + Send + Sync + 'static>(
&mut self,
handle: &Handle<E>,
on_event: impl Fn(&mut T, Handle<E>, &E::Event, &mut ModelContext<'_, T>)
+ Send
+ Sync
+ 'static,
) -> Subscription {
let this = self.handle();
let handle = handle.downgrade();
self.app.event_handlers.insert(
handle.id,
Box::new(move |event, cx| {
let event = event.downcast_ref().expect("invalid event type");
if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
this.update(cx, |this, cx| on_event(this, handle, event, cx));
true
} else {
false
}
}),
)
}
pub fn on_release(
&mut self,
on_release: impl Fn(&mut T, &mut AppContext) + Send + Sync + 'static,
) -> Subscription {
self.app.release_handlers.insert(
self.entity_id,
Box::new(move |this, cx| {
let this = this.downcast_mut().expect("invalid entity type");
on_release(this, cx);
}),
)
}
pub fn observe_release<E: Send + Sync + 'static>(
&mut self,
handle: &Handle<E>,
on_release: impl Fn(&mut T, &mut E, &mut ModelContext<'_, T>) + Send + Sync + 'static,
) -> Subscription {
let this = self.handle();
self.app.release_handlers.insert(
handle.id,
Box::new(move |entity, cx| {
let entity = entity.downcast_mut().expect("invalid entity type");
if let Some(this) = this.upgrade(cx) {
this.update(cx, |this, cx| on_release(this, entity, cx));
}
}),
)
}
pub fn notify(&mut self) {
self.app.push_effect(Effect::Notify {
emitter: self.entity_id,
});
}
}
impl<'a, T: EventEmitter + Send + Sync + 'static> ModelContext<'a, T> {
pub fn emit(&mut self, event: T::Event) {
self.app.push_effect(Effect::Emit {
emitter: self.entity_id,
event: Box::new(event),
});
}
}
impl<'a, T: 'static> Context for ModelContext<'a, T> {
type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>;
type Result<U> = U;
fn entity<U: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U,
) -> Handle<U> {
self.app.entity(build_entity)
}
fn update_entity<U: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<U>,
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
) -> R {
self.app.update_entity(handle, update)
}
}
+64
View File
@@ -0,0 +1,64 @@
use crate::{size, DevicePixels, Result, SharedString, Size};
use anyhow::anyhow;
use image::{Bgra, ImageBuffer};
use std::{
borrow::Cow,
fmt,
hash::Hash,
sync::atomic::{AtomicUsize, Ordering::SeqCst},
};
pub trait AssetSource: 'static + Send + Sync {
fn load(&self, path: &SharedString) -> Result<Cow<[u8]>>;
fn list(&self, path: &SharedString) -> Result<Vec<SharedString>>;
}
impl AssetSource for () {
fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
Err(anyhow!(
"get called on empty asset provider with \"{}\"",
path
))
}
fn list(&self, _path: &SharedString) -> Result<Vec<SharedString>> {
Ok(vec![])
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ImageId(usize);
pub struct ImageData {
pub id: ImageId,
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
}
impl ImageData {
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
Self {
id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
data,
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn size(&self) -> Size<DevicePixels> {
let (width, height) = self.data.dimensions();
size(width.into(), height.into())
}
}
impl fmt::Debug for ImageData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ImageData")
.field("id", &self.id)
.field("size", &self.data.dimensions())
.finish()
}
}
+246
View File
@@ -0,0 +1,246 @@
#![allow(dead_code)]
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 {}
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.,
}
}
pub fn white() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 1.,
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))
}
}
+202
View File
@@ -0,0 +1,202 @@
use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, ViewContext};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::mem;
pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
type ViewState: 'static + Send + Sync;
type ElementState: 'static + Send + Sync;
fn id(&self) -> Option<ElementId>;
fn initialize(
&mut self,
view_state: &mut Self::ViewState,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> Self::ElementState;
fn layout(
&mut self,
view_state: &mut Self::ViewState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId;
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view_state: &mut Self::ViewState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
);
}
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
pub trait ParentElement: Element {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]>;
fn child(mut self, child: impl IntoAnyElement<Self::ViewState>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_any());
self
}
fn children(
mut self,
iter: impl IntoIterator<Item = impl IntoAnyElement<Self::ViewState>>,
) -> Self
where
Self: Sized,
{
self.children_mut()
.extend(iter.into_iter().map(|item| item.into_any()));
self
}
}
trait ElementObject<V>: 'static + Send + Sync {
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
}
struct RenderedElement<E: Element> {
element: E,
phase: ElementRenderPhase<E::ElementState>,
}
#[derive(Default)]
enum ElementRenderPhase<V> {
#[default]
Start,
Initialized {
frame_state: Option<V>,
},
LayoutRequested {
layout_id: LayoutId,
frame_state: Option<V>,
},
Painted,
}
/// Internal struct that wraps an element to store Layout and ElementState 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::Start,
}
}
}
impl<E> ElementObject<E::ViewState> for RenderedElement<E>
where
E: Element,
{
fn initialize(&mut self, view_state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) {
let frame_state = if let Some(id) = self.element.id() {
cx.with_element_state(id, |element_state, cx| {
let element_state = self.element.initialize(view_state, element_state, cx);
((), element_state)
});
None
} else {
let frame_state = self.element.initialize(view_state, None, cx);
Some(frame_state)
};
self.phase = ElementRenderPhase::Initialized { frame_state };
}
fn layout(&mut self, state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) -> LayoutId {
let layout_id;
let mut frame_state;
match mem::take(&mut self.phase) {
ElementRenderPhase::Initialized {
frame_state: initial_frame_state,
} => {
frame_state = initial_frame_state;
if let Some(id) = self.element.id() {
layout_id = cx.with_element_state(id, |element_state, cx| {
let mut element_state = element_state.unwrap();
let layout_id = self.element.layout(state, &mut element_state, cx);
(layout_id, element_state)
});
} else {
layout_id = self
.element
.layout(state, frame_state.as_mut().unwrap(), cx);
}
}
_ => panic!("must call initialize before layout"),
};
self.phase = ElementRenderPhase::LayoutRequested {
layout_id,
frame_state,
};
layout_id
}
fn paint(&mut self, view_state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) {
self.phase = match mem::take(&mut self.phase) {
ElementRenderPhase::LayoutRequested {
layout_id,
mut frame_state,
} => {
let bounds = cx.layout_bounds(layout_id);
if let Some(id) = self.element.id() {
cx.with_element_state(id, |element_state, cx| {
let mut element_state = element_state.unwrap();
self.element
.paint(bounds, view_state, &mut element_state, cx);
((), element_state)
});
} else {
self.element
.paint(bounds, view_state, frame_state.as_mut().unwrap(), cx);
}
ElementRenderPhase::Painted
}
_ => panic!("must call layout before paint"),
};
}
}
pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
impl<V: 'static + Send + Sync> AnyElement<V> {
pub fn new<E: Element<ViewState = V>>(element: E) -> Self {
AnyElement(Box::new(RenderedElement::new(element)))
}
pub fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
self.0.initialize(view_state, cx);
}
pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
self.0.layout(view_state, cx)
}
pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
self.0.paint(view_state, cx)
}
}
pub trait IntoAnyElement<V> {
fn into_any(self) -> AnyElement<V>;
}
impl<V> IntoAnyElement<V> for AnyElement<V> {
fn into_any(self) -> AnyElement<V> {
self
}
}
+9
View File
@@ -0,0 +1,9 @@
mod div;
mod img;
mod svg;
mod text;
pub use div::*;
pub use img::*;
pub use svg::*;
pub use text::*;
+370
View File
@@ -0,0 +1,370 @@
use crate::{
point, AnyElement, BorrowWindow, Bounds, Element, ElementFocus, ElementId, ElementInteraction,
FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, GlobalElementId,
GroupBounds, InteractiveElementState, IntoAnyElement, LayoutId, Overflow, ParentElement,
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
};
use refineable::Refineable;
use smallvec::SmallVec;
pub struct Div<
V: 'static + Send + Sync,
I: ElementInteraction<V> = StatelessInteraction<V>,
F: ElementFocus<V> = FocusDisabled,
> {
interaction: I,
focus: F,
children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>,
base_style: StyleRefinement,
}
pub fn div<V>() -> Div<V, StatelessInteraction<V>, FocusDisabled>
where
V: 'static + Send + Sync,
{
Div {
interaction: StatelessInteraction::default(),
focus: FocusDisabled,
children: SmallVec::new(),
group: None,
base_style: StyleRefinement::default(),
}
}
impl<V, F> Div<V, StatelessInteraction<V>, F>
where
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteraction<V>, F> {
Div {
interaction: id.into().into(),
focus: self.focus,
children: self.children,
group: self.group,
base_style: self.base_style,
}
}
}
impl<V, I, F> Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
self.group = Some(group.into());
self
}
pub fn z_index(mut self, z_index: u32) -> Self {
self.base_style.z_index = Some(z_index);
self
}
pub fn overflow_hidden(mut self) -> Self {
self.base_style.overflow.x = Some(Overflow::Hidden);
self.base_style.overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_x(mut self) -> Self {
self.base_style.overflow.x = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_y(mut self) -> Self {
self.base_style.overflow.y = Some(Overflow::Hidden);
self
}
fn with_element_id<R>(
&mut self,
cx: &mut ViewContext<V>,
f: impl FnOnce(&mut Self, Option<GlobalElementId>, &mut ViewContext<V>) -> R,
) -> R {
if let Some(id) = self.id() {
cx.with_element_id(id, |global_id, cx| f(self, Some(global_id), cx))
} else {
f(self, None, cx)
}
}
pub fn compute_style(
&self,
bounds: Bounds<Pixels>,
element_state: &DivState,
cx: &mut ViewContext<V>,
) -> Style {
let mut computed_style = Style::default();
computed_style.refine(&self.base_style);
self.focus.refine_style(&mut computed_style, cx);
self.interaction
.refine_style(&mut computed_style, bounds, &element_state.interactive, cx);
computed_style
}
}
impl<V> Div<V, StatefulInteraction<V>, FocusDisabled>
where
V: 'static + Send + Sync,
{
pub fn focusable(self) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
Div {
interaction: self.interaction,
focus: FocusEnabled::new(),
children: self.children,
group: self.group,
base_style: self.base_style,
}
}
pub fn track_focus(
self,
handle: &FocusHandle,
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
Div {
interaction: self.interaction,
focus: FocusEnabled::tracked(handle),
children: self.children,
group: self.group,
base_style: self.base_style,
}
}
pub fn overflow_scroll(mut self) -> Self {
self.base_style.overflow.x = Some(Overflow::Scroll);
self.base_style.overflow.y = Some(Overflow::Scroll);
self
}
pub fn overflow_x_scroll(mut self) -> Self {
self.base_style.overflow.x = Some(Overflow::Scroll);
self
}
pub fn overflow_y_scroll(mut self) -> Self {
self.base_style.overflow.y = Some(Overflow::Scroll);
self
}
}
impl<V> Div<V, StatelessInteraction<V>, FocusDisabled>
where
V: 'static + Send + Sync,
{
pub fn track_focus(
self,
handle: &FocusHandle,
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
Div {
interaction: self.interaction.into_stateful(handle),
focus: handle.clone().into(),
children: self.children,
group: self.group,
base_style: self.base_style,
}
}
}
impl<V, I> Focusable for Div<V, I, FocusEnabled<V>>
where
I: ElementInteraction<V>,
V: 'static + Send + Sync,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
&mut self.focus.focus_listeners
}
fn set_focus_style(&mut self, style: StyleRefinement) {
self.focus.focus_style = style;
}
fn set_focus_in_style(&mut self, style: StyleRefinement) {
self.focus.focus_in_style = style;
}
fn set_in_focus_style(&mut self, style: StyleRefinement) {
self.focus.in_focus_style = style;
}
}
#[derive(Default)]
pub struct DivState {
interactive: InteractiveElementState,
focus_handle: Option<FocusHandle>,
child_layout_ids: SmallVec<[LayoutId; 4]>,
}
impl<V, I, F> Element for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
type ViewState = V;
type ElementState = DivState;
fn id(&self) -> Option<ElementId> {
self.interaction
.as_stateful()
.map(|identified| identified.id.clone())
}
fn initialize(
&mut self,
view_state: &mut Self::ViewState,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
self.focus
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
element_state.focus_handle = focus_handle;
self.interaction.initialize(cx, |cx| {
for child in &mut self.children {
child.initialize(view_state, cx);
}
})
});
element_state
}
fn layout(
&mut self,
view_state: &mut Self::ViewState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId {
let style = self.compute_style(Bounds::default(), element_state, cx);
style.apply_text_style(cx, |cx| {
self.with_element_id(cx, |this, _global_id, cx| {
let layout_ids = this
.children
.iter_mut()
.map(|child| child.layout(view_state, cx))
.collect::<SmallVec<_>>();
element_state.child_layout_ids = layout_ids.clone();
cx.request_layout(&style, layout_ids)
})
})
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view_state: &mut Self::ViewState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) {
self.with_element_id(cx, |this, _global_id, cx| {
if let Some(group) = this.group.clone() {
GroupBounds::push(group, bounds, cx);
}
let style = this.compute_style(bounds, element_state, cx);
let z_index = style.z_index.unwrap_or(0);
let mut child_min = point(Pixels::MAX, Pixels::MAX);
let mut child_max = Point::default();
let content_size = if element_state.child_layout_ids.is_empty() {
bounds.size
} else {
for child_layout_id in &element_state.child_layout_ids {
let child_bounds = cx.layout_bounds(*child_layout_id);
child_min = child_min.min(&child_bounds.origin);
child_max = child_max.max(&child_bounds.lower_right());
}
(child_max - child_min).into()
};
cx.stack(z_index, |cx| {
cx.stack(0, |cx| {
style.paint(bounds, cx);
this.focus.paint(bounds, cx);
this.interaction.paint(
bounds,
content_size,
style.overflow,
&mut element_state.interactive,
cx,
);
});
cx.stack(1, |cx| {
style.apply_text_style(cx, |cx| {
style.apply_overflow(bounds, cx, |cx| {
let scroll_offset = element_state.interactive.scroll_offset();
cx.with_scroll_offset(scroll_offset, |cx| {
for child in &mut this.children {
child.paint(view_state, cx);
}
});
})
})
});
});
if let Some(group) = this.group.as_ref() {
GroupBounds::pop(group, cx);
}
})
}
}
impl<V, I, F> IntoAnyElement<V> for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
fn into_any(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, I, F> ParentElement for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
&mut self.children
}
}
impl<V, I, F> Styled for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
fn style(&mut self) -> &mut StyleRefinement {
&mut self.base_style
}
}
impl<V, I, F> StatelessInteractive for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
self.interaction.as_stateless_mut()
}
}
impl<V, F> StatefulInteractive for Div<V, StatefulInteraction<V>, F>
where
F: ElementFocus<V>,
V: 'static + Send + Sync,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
&mut self.interaction
}
}
+194
View File
@@ -0,0 +1,194 @@
use crate::{
div, AnyElement, BorrowWindow, Bounds, Div, DivState, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, IntoAnyElement,
LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
StatelessInteractive, StyleRefinement, Styled, ViewContext,
};
use futures::FutureExt;
use util::ResultExt;
pub struct Img<
V: 'static + Send + Sync,
I: ElementInteraction<V> = StatelessInteraction<V>,
F: ElementFocus<V> = FocusDisabled,
> {
base: Div<V, I, F>,
uri: Option<SharedString>,
grayscale: bool,
}
pub fn img<V>() -> Img<V, StatelessInteraction<V>, FocusDisabled>
where
V: 'static + Send + Sync,
{
Img {
base: div(),
uri: None,
grayscale: false,
}
}
impl<V, I, F> Img<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
self.uri = Some(uri.into());
self
}
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.grayscale = grayscale;
self
}
}
impl<V, F> Img<V, StatelessInteraction<V>, F>
where
V: 'static + Send + Sync,
F: ElementFocus<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteraction<V>, F> {
Img {
base: self.base.id(id),
uri: self.uri,
grayscale: self.grayscale,
}
}
}
impl<V, I, F> IntoAnyElement<V> for Img<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn into_any(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, I, F> Element for Img<V, I, F>
where
V: Send + Sync + 'static,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
type ViewState = V;
type ElementState = DivState;
fn id(&self) -> Option<crate::ElementId> {
self.base.id()
}
fn initialize(
&mut self,
view_state: &mut V,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<V>,
) -> Self::ElementState {
self.base.initialize(view_state, element_state, cx)
}
fn layout(
&mut self,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId {
self.base.layout(view_state, element_state, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) {
cx.stack(0, |cx| {
self.base.paint(bounds, view, element_state, cx);
});
let style = self.base.compute_style(bounds, element_state, cx);
let corner_radii = style.corner_radii;
if let Some(uri) = self.uri.clone() {
let image_future = cx.image_cache.get(uri);
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(ResultExt::log_err)
{
let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
cx.stack(1, |cx| {
cx.paint_image(bounds, corner_radii, data, self.grayscale)
.log_err()
});
} else {
cx.spawn(|_, mut cx| async move {
if image_future.await.log_err().is_some() {
cx.on_next_frame(|cx| cx.notify());
}
})
.detach()
}
}
}
}
impl<V, I, F> Styled for Img<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
}
impl<V, I, F> StatelessInteractive for Img<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
self.base.stateless_interactivity()
}
}
impl<V, F> StatefulInteractive for Img<V, StatefulInteraction<V>, F>
where
V: 'static + Send + Sync,
F: ElementFocus<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
self.base.stateful_interactivity()
}
}
impl<V, I> Focusable for Img<V, I, FocusEnabled<V>>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<Self::ViewState> {
self.base.focus_listeners()
}
fn set_focus_style(&mut self, style: StyleRefinement) {
self.base.set_focus_style(style)
}
fn set_focus_in_style(&mut self, style: StyleRefinement) {
self.base.set_focus_in_style(style)
}
fn set_in_focus_style(&mut self, style: StyleRefinement) {
self.base.set_in_focus_style(style)
}
}
+168
View File
@@ -0,0 +1,168 @@
use crate::{
div, AnyElement, Bounds, Div, DivState, Element, ElementFocus, ElementId, ElementInteraction,
FocusDisabled, FocusEnabled, FocusListeners, Focusable, IntoAnyElement, LayoutId, Pixels,
SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
StatelessInteractive, StyleRefinement, Styled, ViewContext,
};
use util::ResultExt;
pub struct Svg<
V: 'static + Send + Sync,
I: ElementInteraction<V> = StatelessInteraction<V>,
F: ElementFocus<V> = FocusDisabled,
> {
base: Div<V, I, F>,
path: Option<SharedString>,
}
pub fn svg<V>() -> Svg<V, StatelessInteraction<V>, FocusDisabled>
where
V: 'static + Send + Sync,
{
Svg {
base: div(),
path: None,
}
}
impl<V, I, F> Svg<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
self
}
}
impl<V, F> Svg<V, StatelessInteraction<V>, F>
where
V: 'static + Send + Sync,
F: ElementFocus<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteraction<V>, F> {
Svg {
base: self.base.id(id),
path: self.path,
}
}
}
impl<V, I, F> IntoAnyElement<V> for Svg<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn into_any(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, I, F> Element for Svg<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
type ViewState = V;
type ElementState = DivState;
fn id(&self) -> Option<crate::ElementId> {
self.base.id()
}
fn initialize(
&mut self,
view_state: &mut V,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<V>,
) -> Self::ElementState {
self.base.initialize(view_state, element_state, cx)
}
fn layout(
&mut self,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId {
self.base.layout(view_state, element_state, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view: &mut Self::ViewState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
self.base.paint(bounds, view, element_state, cx);
let color = self
.base
.compute_style(bounds, element_state, cx)
.text
.color;
if let Some((path, color)) = self.path.as_ref().zip(color) {
cx.paint_svg(bounds, path.clone(), color).log_err();
}
}
}
impl<V, I, F> Styled for Svg<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
}
impl<V, I, F> StatelessInteractive for Svg<V, I, F>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
self.base.stateless_interactivity()
}
}
impl<V, F> StatefulInteractive for Svg<V, StatefulInteraction<V>, F>
where
V: 'static + Send + Sync,
F: ElementFocus<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
self.base.stateful_interactivity()
}
}
impl<V, I> Focusable for Svg<V, I, FocusEnabled<V>>
where
V: 'static + Send + Sync,
I: ElementInteraction<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<Self::ViewState> {
self.base.focus_listeners()
}
fn set_focus_style(&mut self, style: StyleRefinement) {
self.base.set_focus_style(style)
}
fn set_focus_in_style(&mut self, style: StyleRefinement) {
self.base.set_focus_in_style(style)
}
fn set_in_focus_style(&mut self, style: StyleRefinement) {
self.base.set_in_focus_style(style)
}
}
+143
View File
@@ -0,0 +1,143 @@
use crate::{
AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
SharedString, Size, ViewContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{marker::PhantomData, sync::Arc};
use util::ResultExt;
impl<S: 'static + Send + Sync> IntoAnyElement<S> for SharedString {
fn into_any(self) -> AnyElement<S> {
Text {
text: self,
state_type: PhantomData,
}
.into_any()
}
}
impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
fn into_any(self) -> AnyElement<V> {
Text {
text: self.into(),
state_type: PhantomData,
}
.into_any()
}
}
// TODO: Figure out how to pass `String` to `child` without this.
// This impl doesn't exist in the `gpui2` crate.
impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
fn into_any(self) -> AnyElement<S> {
Text {
text: self.into(),
state_type: PhantomData,
}
.into_any()
}
}
pub struct Text<V> {
text: SharedString,
state_type: PhantomData<V>,
}
impl<V: 'static + Send + Sync> IntoAnyElement<V> for Text<V> {
fn into_any(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V: 'static + Send + Sync> Element for Text<V> {
type ViewState = V;
type ElementState = Arc<Mutex<Option<TextElementState>>>;
fn id(&self) -> Option<crate::ElementId> {
None
}
fn initialize(
&mut self,
_view_state: &mut V,
element_state: Option<Self::ElementState>,
_cx: &mut ViewContext<V>,
) -> Self::ElementState {
element_state.unwrap_or_default()
}
fn layout(
&mut self,
_view: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) -> LayoutId {
let text_system = cx.text_system().clone();
let text_style = cx.text_style();
let font_size = text_style.font_size * cx.rem_size();
let line_height = text_style
.line_height
.to_pixels(font_size.into(), cx.rem_size());
let text = self.text.clone();
let rem_size = cx.rem_size();
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone();
move |known_dimensions, _| {
let Some(lines) = text_system
.layout_text(
&text,
font_size,
&[text_style.to_run(text.len())],
known_dimensions.width, // Wrap if we know the width.
)
.log_err()
else {
return Size::default();
};
let line_count = lines
.iter()
.map(|line| line.wrap_count() + 1)
.sum::<usize>();
let size = Size {
width: lines.iter().map(|line| line.layout.width).max().unwrap(),
height: line_height * line_count,
};
element_state
.lock()
.replace(TextElementState { lines, line_height });
size
}
});
layout_id
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) {
let element_state = element_state.lock();
let element_state = element_state
.as_ref()
.expect("measurement has not been performed");
let line_height = element_state.line_height;
let mut line_origin = bounds.origin;
for line in &element_state.lines {
line.paint(line_origin, line_height, cx).log_err();
line_origin.y += line.size(line_height).height;
}
}
}
pub struct TextElementState {
lines: SmallVec<[Line; 1]>,
line_height: Pixels,
}
+122
View File
@@ -0,0 +1,122 @@
use crate::PlatformDispatcher;
use smol::prelude::*;
use std::{
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
#[derive(Clone)]
pub struct Executor {
dispatcher: Arc<dyn PlatformDispatcher>,
}
#[must_use]
pub enum Task<T> {
Ready(Option<T>),
Spawned(async_task::Task<T>),
}
impl<T> Task<T> {
pub fn ready(val: T) -> Self {
Task::Ready(Some(val))
}
pub fn detach(self) {
match self {
Task::Ready(_) => {}
Task::Spawned(task) => task.detach(),
}
}
}
impl<T> Future for Task<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match unsafe { self.get_unchecked_mut() } {
Task::Ready(val) => Poll::Ready(val.take().unwrap()),
Task::Spawned(task) => task.poll(cx),
}
}
}
impl Executor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher }
}
/// Enqueues the given closure to be run on any thread. The closure returns
/// a future which will be run to completion on any available thread.
pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
let dispatcher = self.dispatcher.clone();
let (runnable, task) =
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
runnable.schedule();
Task::Spawned(task)
}
/// Enqueues the given closure to run on the application's event loop.
/// Returns the result asynchronously.
pub fn run_on_main<F, R>(&self, func: F) -> Task<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
if self.dispatcher.is_main_thread() {
Task::ready(func())
} else {
self.spawn_on_main(move || async move { func() })
}
}
/// Enqueues the given closure to be run on the application's event loop. The
/// closure returns a future which will be run to completion on the main thread.
pub fn spawn_on_main<F, R>(&self, func: impl FnOnce() -> F + Send + 'static) -> Task<R>
where
F: Future<Output = R> + 'static,
R: Send + 'static,
{
let (runnable, task) = async_task::spawn(
{
let this = self.clone();
async move {
let task = this.spawn_on_main_local(func());
task.await
}
},
{
let dispatcher = self.dispatcher.clone();
move |runnable| dispatcher.dispatch_on_main_thread(runnable)
},
);
runnable.schedule();
Task::Spawned(task)
}
/// Enqueues the given closure to be run on the application's event loop. Must
/// be called on the main thread.
pub fn spawn_on_main_local<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
where
R: 'static,
{
assert!(
self.dispatcher.is_main_thread(),
"must be called on main thread"
);
let dispatcher = self.dispatcher.clone();
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
dispatcher.dispatch_on_main_thread(runnable)
});
runnable.schedule();
Task::Spawned(task)
}
pub fn is_main_thread(&self) -> bool {
self.dispatcher.is_main_thread()
}
}
+277
View File
@@ -0,0 +1,277 @@
use crate::{
Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, MouseDownEvent, Pixels, Style,
StyleRefinement, ViewContext, WindowContext,
};
use refineable::Refineable;
use smallvec::SmallVec;
use std::sync::Arc;
pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
pub type FocusListener<V> =
Arc<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
pub trait Focusable: Element {
fn focus_listeners(&mut self) -> &mut FocusListeners<Self::ViewState>;
fn set_focus_style(&mut self, style: StyleRefinement);
fn set_focus_in_style(&mut self, style: StyleRefinement);
fn set_in_focus_style(&mut self, style: StyleRefinement);
fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_focus_style(f(StyleRefinement::default()));
self
}
fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_focus_in_style(f(StyleRefinement::default()));
self
}
fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_in_focus_style(f(StyleRefinement::default()));
self
}
fn on_focus(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Arc::new(move |view, focus_handle, event, cx| {
if event.focused.as_ref() == Some(focus_handle) {
listener(view, event, cx)
}
}));
self
}
fn on_blur(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Arc::new(move |view, focus_handle, event, cx| {
if event.blurred.as_ref() == Some(focus_handle) {
listener(view, event, cx)
}
}));
self
}
fn on_focus_in(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Arc::new(move |view, focus_handle, event, cx| {
let descendant_blurred = event
.blurred
.as_ref()
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
let descendant_focused = event
.focused
.as_ref()
.map_or(false, |focused| focus_handle.contains(focused, cx));
if !descendant_blurred && descendant_focused {
listener(view, event, cx)
}
}));
self
}
fn on_focus_out(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Arc::new(move |view, focus_handle, event, cx| {
let descendant_blurred = event
.blurred
.as_ref()
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
let descendant_focused = event
.focused
.as_ref()
.map_or(false, |focused| focus_handle.contains(focused, cx));
if descendant_blurred && !descendant_focused {
listener(view, event, cx)
}
}));
self
}
}
pub trait ElementFocus<V: 'static + Send + Sync>: 'static + Send + Sync {
fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>>;
fn initialize<R>(
&mut self,
focus_handle: Option<FocusHandle>,
cx: &mut ViewContext<V>,
f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
) -> R {
if let Some(focusable) = self.as_focusable_mut() {
let focus_handle = focusable
.focus_handle
.get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
.clone();
for listener in focusable.focus_listeners.iter().cloned() {
let focus_handle = focus_handle.clone();
cx.on_focus_changed(move |view, event, cx| {
listener(view, &focus_handle, event, cx)
});
}
cx.with_focus(focus_handle.clone(), |cx| f(Some(focus_handle), cx))
} else {
f(None, cx)
}
}
fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
if let Some(focusable) = self.as_focusable() {
let focus_handle = focusable
.focus_handle
.as_ref()
.expect("must call initialize before refine_style");
if focus_handle.contains_focused(cx) {
style.refine(&focusable.focus_in_style);
}
if focus_handle.within_focused(cx) {
style.refine(&focusable.in_focus_style);
}
if focus_handle.is_focused(cx) {
style.refine(&focusable.focus_style);
}
}
}
fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
if let Some(focusable) = self.as_focusable() {
let focus_handle = focusable
.focus_handle
.clone()
.expect("must call initialize before paint");
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
if !cx.default_prevented() {
cx.focus(&focus_handle);
cx.prevent_default();
}
}
})
}
}
}
pub struct FocusEnabled<V: 'static + Send + Sync> {
pub focus_handle: Option<FocusHandle>,
pub focus_listeners: FocusListeners<V>,
pub focus_style: StyleRefinement,
pub focus_in_style: StyleRefinement,
pub in_focus_style: StyleRefinement,
}
impl<V> FocusEnabled<V>
where
V: 'static + Send + Sync,
{
pub fn new() -> Self {
Self {
focus_handle: None,
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
pub fn tracked(handle: &FocusHandle) -> Self {
Self {
focus_handle: Some(handle.clone()),
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
}
impl<V> ElementFocus<V> for FocusEnabled<V>
where
V: 'static + Send + Sync,
{
fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
Some(self)
}
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
Some(self)
}
}
impl<V> From<FocusHandle> for FocusEnabled<V>
where
V: 'static + Send + Sync,
{
fn from(value: FocusHandle) -> Self {
Self {
focus_handle: Some(value),
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
}
pub struct FocusDisabled;
impl<V> ElementFocus<V> for FocusDisabled
where
V: 'static + Send + Sync,
{
fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
None
}
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
None
}
}
File diff suppressed because it is too large Load Diff
+273
View File
@@ -0,0 +1,273 @@
mod action;
mod app;
mod assets;
mod color;
mod element;
mod elements;
mod executor;
mod focusable;
mod geometry;
mod image_cache;
mod interactive;
mod keymap;
mod platform;
mod scene;
mod style;
mod styled;
mod subscription;
mod svg_renderer;
mod taffy;
mod text_system;
mod util;
mod view;
mod window;
pub use action::*;
pub use anyhow::Result;
pub use app::*;
pub use assets::*;
pub use color::*;
pub use element::*;
pub use elements::*;
pub use executor::*;
pub use focusable::*;
pub use geometry::*;
pub use gpui2_macros::*;
pub use image_cache::*;
pub use interactive::*;
pub use keymap::*;
pub use platform::*;
pub use refineable::*;
pub use scene::*;
pub use serde;
pub use serde_json;
pub use smallvec;
pub use smol::Timer;
pub use style::*;
pub use styled::*;
pub use subscription::*;
pub use svg_renderer::*;
pub use taffy::{AvailableSpace, LayoutId};
pub use text_system::*;
pub use util::arc_cow::ArcCow;
pub use view::*;
pub use window::*;
use derive_more::{Deref, DerefMut};
use std::{
any::{Any, TypeId},
mem,
ops::{Deref, DerefMut},
sync::Arc,
};
use taffy::TaffyLayoutEngine;
type AnyBox = Box<dyn Any + Send + Sync>;
pub trait Context {
type EntityContext<'a, 'w, T: 'static + Send + Sync>;
type Result<T>;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Self::Result<Handle<T>>;
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> Self::Result<R>;
}
pub enum GlobalKey {
Numeric(usize),
View(EntityId),
Type(TypeId),
}
#[repr(transparent)]
pub struct MainThread<T>(T);
impl<T> Deref for MainThread<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for MainThread<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<C: Context> Context for MainThread<C> {
type EntityContext<'a, 'w, T: 'static + Send + Sync> = MainThread<C::EntityContext<'a, 'w, T>>;
type Result<T> = C::Result<T>;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Self::Result<Handle<T>> {
self.0.entity(|cx| {
let cx = unsafe {
mem::transmute::<
&mut C::EntityContext<'_, '_, T>,
&mut MainThread<C::EntityContext<'_, '_, T>>,
>(cx)
};
build_entity(cx)
})
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> Self::Result<R> {
self.0.update_entity(handle, |entity, cx| {
let cx = unsafe {
mem::transmute::<
&mut C::EntityContext<'_, '_, T>,
&mut MainThread<C::EntityContext<'_, '_, T>>,
>(cx)
};
update(entity, cx)
})
}
}
pub trait BorrowAppContext {
fn app_mut(&mut self) -> &mut AppContext;
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.app_mut().push_text_style(style);
let result = f(self);
self.app_mut().pop_text_style();
result
}
fn with_global<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.app_mut().push_global(state);
let result = f(self);
self.app_mut().pop_global::<T>();
result
}
}
pub trait EventEmitter {
type Event: Any + Send + Sync + 'static;
}
pub trait Flatten<T> {
fn flatten(self) -> Result<T>;
}
impl<T> Flatten<T> for Result<Result<T>> {
fn flatten(self) -> Result<T> {
self?
}
}
impl<T> Flatten<T> for Result<T> {
fn flatten(self) -> Result<T> {
self
}
}
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
pub struct SharedString(ArcCow<'static, str>);
impl Default for SharedString {
fn default() -> Self {
Self(ArcCow::Owned("".into()))
}
}
impl AsRef<str> for SharedString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Debug for SharedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Display for SharedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_ref())
}
}
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
fn from(value: T) -> Self {
Self(value.into())
}
}
pub enum Reference<'a, T> {
Immutable(&'a T),
Mutable(&'a mut T),
}
impl<'a, T> Deref for Reference<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Reference::Immutable(target) => target,
Reference::Mutable(target) => target,
}
}
}
impl<'a, T> DerefMut for Reference<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Reference::Immutable(_) => {
panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
}
Reference::Mutable(target) => target,
}
}
}
pub(crate) struct MainThreadOnly<T: ?Sized> {
executor: Executor,
value: Arc<T>,
}
impl<T: ?Sized> Clone for MainThreadOnly<T> {
fn clone(&self) -> Self {
Self {
executor: self.executor.clone(),
value: self.value.clone(),
}
}
}
/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type
/// to become `Send`.
impl<T: 'static + ?Sized> MainThreadOnly<T> {
pub(crate) fn new(value: Arc<T>, executor: Executor) -> Self {
Self { executor, value }
}
pub(crate) fn borrow_on_main_thread(&self) -> &T {
assert!(self.executor.is_main_thread());
&self.value
}
}
unsafe impl<T: ?Sized> Send for MainThreadOnly<T> {}
+99
View File
@@ -0,0 +1,99 @@
use crate::{ImageData, ImageId, SharedString};
use collections::HashMap;
use futures::{
future::{BoxFuture, Shared},
AsyncReadExt, FutureExt,
};
use image::ImageError;
use parking_lot::Mutex;
use std::sync::Arc;
use thiserror::Error;
use util::http::{self, HttpClient};
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct RenderImageParams {
pub(crate) image_id: ImageId,
}
#[derive(Debug, Error, Clone)]
pub enum Error {
#[error("http error: {0}")]
Client(#[from] http::Error),
#[error("IO error: {0}")]
Io(Arc<std::io::Error>),
#[error("unexpected http status: {status}, body: {body}")]
BadStatus {
status: http::StatusCode,
body: String,
},
#[error("image error: {0}")]
Image(Arc<ImageError>),
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Io(Arc::new(error))
}
}
impl From<ImageError> for Error {
fn from(error: ImageError) -> Self {
Error::Image(Arc::new(error))
}
}
pub struct ImageCache {
client: Arc<dyn HttpClient>,
images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
}
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
impl ImageCache {
pub fn new(client: Arc<dyn HttpClient>) -> Self {
ImageCache {
client,
images: Default::default(),
}
}
pub fn get(
&self,
uri: impl Into<SharedString>,
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
let uri = uri.into();
let mut images = self.images.lock();
match images.get(&uri) {
Some(future) => future.clone(),
None => {
let client = self.client.clone();
let future = {
let uri = uri.clone();
async move {
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
if !response.status().is_success() {
return Err(Error::BadStatus {
status: response.status(),
body: String::from_utf8_lossy(&body).into_owned(),
});
}
let format = image::guess_format(&body)?;
let image =
image::load_from_memory_with_format(&body, format)?.into_bgra8();
Ok(Arc::new(ImageData::new(image)))
}
}
.boxed()
.shared();
images.insert(uri, future.clone());
future
}
}
}
}
+950
View File
@@ -0,0 +1,950 @@
use crate::{
point, px, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element,
ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, SharedString,
Size, Style, StyleRefinement, ViewContext,
};
use collections::HashMap;
use derive_more::{Deref, DerefMut};
use parking_lot::Mutex;
use refineable::Refineable;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
fmt::Debug,
ops::Deref,
sync::Arc,
};
pub trait StatelessInteractive: Element {
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.stateless_interactivity().hover_style = f(StyleRefinement::default());
self
}
fn group_hover(
mut self,
group_name: impl Into<SharedString>,
f: impl FnOnce(StyleRefinement) -> StyleRefinement,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().group_hover_style = Some(GroupStyle {
group: group_name.into(),
style: f(StyleRefinement::default()),
});
self
}
fn on_mouse_down(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity()
.mouse_down_listeners
.push(Arc::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == button
&& bounds.contains_point(&event.position)
{
handler(view, event, cx)
}
}));
self
}
fn on_mouse_up(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity()
.mouse_up_listeners
.push(Arc::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == button
&& bounds.contains_point(&event.position)
{
handler(view, event, cx)
}
}));
self
}
fn on_mouse_down_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity()
.mouse_down_listeners
.push(Arc::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture
&& event.button == button
&& !bounds.contains_point(&event.position)
{
handler(view, event, cx)
}
}));
self
}
fn on_mouse_up_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity()
.mouse_up_listeners
.push(Arc::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture
&& event.button == button
&& !bounds.contains_point(&event.position)
{
handler(view, event, cx);
}
}));
self
}
fn on_mouse_move(
mut self,
handler: impl Fn(&mut Self::ViewState, &MouseMoveEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity()
.mouse_move_listeners
.push(Arc::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
handler(view, event, cx);
}
}));
self
}
fn on_scroll_wheel(
mut self,
handler: impl Fn(&mut Self::ViewState, &ScrollWheelEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity()
.scroll_wheel_listeners
.push(Arc::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
handler(view, event, cx);
}
}));
self
}
fn context<C>(mut self, context: C) -> Self
where
Self: Sized,
C: TryInto<DispatchContext>,
C::Error: Debug,
{
self.stateless_interactivity().dispatch_context =
context.try_into().expect("invalid dispatch context");
self
}
fn on_action<A: 'static>(
mut self,
listener: impl Fn(&mut Self::ViewState, &A, DispatchPhase, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<A>(),
Arc::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
listener(view, event, phase, cx);
None
}),
));
self
}
fn on_key_down(
mut self,
listener: impl Fn(
&mut Self::ViewState,
&KeyDownEvent,
DispatchPhase,
&mut ViewContext<Self::ViewState>,
) + Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyDownEvent>(),
Arc::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
listener(view, event, phase, cx);
None
}),
));
self
}
fn on_key_up(
mut self,
listener: impl Fn(&mut Self::ViewState, &KeyUpEvent, DispatchPhase, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyUpEvent>(),
Arc::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
listener(view, event, phase, cx);
None
}),
));
self
}
}
pub trait StatefulInteractive: StatelessInteractive {
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState>;
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.stateful_interactivity().active_style = f(StyleRefinement::default());
self
}
fn group_active(
mut self,
group_name: impl Into<SharedString>,
f: impl FnOnce(StyleRefinement) -> StyleRefinement,
) -> Self
where
Self: Sized,
{
self.stateful_interactivity().group_active_style = Some(GroupStyle {
group: group_name.into(),
style: f(StyleRefinement::default()),
});
self
}
fn on_click(
mut self,
handler: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
) -> Self
where
Self: Sized,
{
self.stateful_interactivity()
.mouse_click_listeners
.push(Arc::new(move |view, event, cx| handler(view, event, cx)));
self
}
}
pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
fn as_stateless(&self) -> &StatelessInteraction<V>;
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
fn as_stateful(&self) -> Option<&StatefulInteraction<V>>;
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>>;
fn initialize<R>(
&mut self,
cx: &mut ViewContext<V>,
f: impl FnOnce(&mut ViewContext<V>) -> R,
) -> R {
if let Some(stateful) = self.as_stateful_mut() {
cx.with_element_id(stateful.id.clone(), |global_id, cx| {
stateful.key_listeners.push((
TypeId::of::<KeyDownEvent>(),
Arc::new(move |_, key_down, context, phase, cx| {
if phase == DispatchPhase::Bubble {
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
if let KeyMatch::Some(action) =
cx.match_keystroke(&global_id, &key_down.keystroke, context)
{
return Some(action);
}
}
None
}),
));
let result = stateful.stateless.initialize(cx, f);
stateful.key_listeners.pop();
result
})
} else {
let stateless = self.as_stateless();
cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| {
cx.with_key_listeners(&stateless.key_listeners, f)
})
}
}
fn refine_style(
&self,
style: &mut Style,
bounds: Bounds<Pixels>,
element_state: &InteractiveElementState,
cx: &mut ViewContext<V>,
) {
let mouse_position = cx.mouse_position();
let stateless = self.as_stateless();
if let Some(group_hover) = stateless.group_hover_style.as_ref() {
if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
if group_bounds.contains_point(&mouse_position) {
style.refine(&group_hover.style);
}
}
}
if bounds.contains_point(&mouse_position) {
style.refine(&stateless.hover_style);
}
if let Some(stateful) = self.as_stateful() {
let active_state = element_state.active_state.lock();
if active_state.group {
if let Some(group_style) = stateful.group_active_style.as_ref() {
style.refine(&group_style.style);
}
}
if active_state.element {
style.refine(&stateful.active_style);
}
}
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
content_size: Size<Pixels>,
overflow: Point<Overflow>,
element_state: &mut InteractiveElementState,
cx: &mut ViewContext<V>,
) {
let stateless = self.as_stateless();
for listener in stateless.mouse_down_listeners.iter().cloned() {
cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
listener(state, event, &bounds, phase, cx);
})
}
for listener in stateless.mouse_up_listeners.iter().cloned() {
cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
listener(state, event, &bounds, phase, cx);
})
}
for listener in stateless.mouse_move_listeners.iter().cloned() {
cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
listener(state, event, &bounds, phase, cx);
})
}
for listener in stateless.scroll_wheel_listeners.iter().cloned() {
cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
listener(state, event, &bounds, phase, cx);
})
}
let hover_group_bounds = stateless
.group_hover_style
.as_ref()
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
if let Some(group_bounds) = hover_group_bounds {
paint_hover_listener(group_bounds, cx);
}
if stateless.hover_style.is_some() {
paint_hover_listener(bounds, cx);
}
if let Some(stateful) = self.as_stateful() {
let click_listeners = stateful.mouse_click_listeners.clone();
let pending_click = element_state.pending_click.clone();
let mouse_down = pending_click.lock().clone();
if let Some(mouse_down) = mouse_down {
cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
let mouse_click = MouseClickEvent {
down: mouse_down.clone(),
up: event.clone(),
};
for listener in &click_listeners {
listener(state, &mouse_click, cx);
}
}
*pending_click.lock() = None;
});
} else {
cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
*pending_click.lock() = Some(event.clone());
}
});
}
let active_state = element_state.active_state.clone();
if active_state.lock().is_none() {
let active_group_bounds = stateful
.group_active_style
.as_ref()
.and_then(|group_active| GroupBounds::get(&group_active.group, cx));
cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
let group = active_group_bounds
.map_or(false, |bounds| bounds.contains_point(&down.position));
let element = bounds.contains_point(&down.position);
if group || element {
*active_state.lock() = ActiveState { group, element };
cx.notify();
}
}
});
} else {
cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Capture {
*active_state.lock() = ActiveState::default();
cx.notify();
}
});
}
if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
let scroll_offset = element_state
.scroll_offset
.get_or_insert_with(Arc::default)
.clone();
let line_height = cx.line_height();
let scroll_max = (content_size - bounds.size).max(&Size::default());
cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
let mut scroll_offset = scroll_offset.lock();
let old_scroll_offset = *scroll_offset;
let delta = event.delta.pixel_delta(line_height);
if overflow.x == Overflow::Scroll {
scroll_offset.x =
(scroll_offset.x - delta.x).clamp(px(0.), scroll_max.width);
}
if overflow.y == Overflow::Scroll {
scroll_offset.y =
(scroll_offset.y - delta.y).clamp(px(0.), scroll_max.height);
}
if *scroll_offset != old_scroll_offset {
cx.notify();
cx.stop_propagation();
}
}
});
}
}
}
}
fn paint_hover_listener<V>(bounds: Bounds<Pixels>, cx: &mut ViewContext<V>)
where
V: 'static + Send + Sync,
{
let hovered = bounds.contains_point(&cx.mouse_position());
cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Capture {
if bounds.contains_point(&event.position) != hovered {
cx.notify();
}
}
});
}
#[derive(Deref, DerefMut)]
pub struct StatefulInteraction<V: 'static + Send + Sync> {
pub id: ElementId,
#[deref]
#[deref_mut]
stateless: StatelessInteraction<V>,
pub mouse_click_listeners: SmallVec<[MouseClickListener<V>; 2]>,
pub active_style: StyleRefinement,
pub group_active_style: Option<GroupStyle>,
}
impl<V> ElementInteraction<V> for StatefulInteraction<V>
where
V: 'static + Send + Sync,
{
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
Some(self)
}
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
Some(self)
}
fn as_stateless(&self) -> &StatelessInteraction<V> {
&self.stateless
}
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
&mut self.stateless
}
}
impl<V> From<ElementId> for StatefulInteraction<V>
where
V: 'static + Send + Sync,
{
fn from(id: ElementId) -> Self {
Self {
id,
stateless: StatelessInteraction::default(),
mouse_click_listeners: SmallVec::new(),
active_style: StyleRefinement::default(),
group_active_style: None,
}
}
}
pub struct StatelessInteraction<V> {
pub dispatch_context: DispatchContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
pub hover_style: StyleRefinement,
pub group_hover_style: Option<GroupStyle>,
}
impl<V> StatelessInteraction<V>
where
V: 'static + Send + Sync,
{
pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteraction<V> {
StatefulInteraction {
id: id.into(),
stateless: self,
mouse_click_listeners: SmallVec::new(),
active_style: StyleRefinement::default(),
group_active_style: None,
}
}
}
pub struct GroupStyle {
pub group: SharedString,
pub style: StyleRefinement,
}
#[derive(Default)]
pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
impl GroupBounds {
pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
cx.default_global::<Self>()
.0
.get(name)
.and_then(|bounds_stack| bounds_stack.last())
.cloned()
}
pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
cx.default_global::<Self>()
.0
.entry(name)
.or_default()
.push(bounds);
}
pub fn pop(name: &SharedString, cx: &mut AppContext) {
cx.default_global::<GroupBounds>()
.0
.get_mut(name)
.unwrap()
.pop();
}
}
#[derive(Copy, Clone, Default, Eq, PartialEq)]
struct ActiveState {
pub group: bool,
pub element: bool,
}
impl ActiveState {
pub fn is_none(&self) -> bool {
!self.group && !self.element
}
}
#[derive(Default)]
pub struct InteractiveElementState {
active_state: Arc<Mutex<ActiveState>>,
pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
}
impl InteractiveElementState {
pub fn scroll_offset(&self) -> Option<Point<Pixels>> {
self.scroll_offset
.as_ref()
.map(|offset| offset.lock().clone())
}
}
impl<V> Default for StatelessInteraction<V> {
fn default() -> Self {
Self {
dispatch_context: DispatchContext::default(),
mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(),
scroll_wheel_listeners: SmallVec::new(),
key_listeners: SmallVec::new(),
hover_style: StyleRefinement::default(),
group_hover_style: None,
}
}
}
impl<V> ElementInteraction<V> for StatelessInteraction<V>
where
V: 'static + Send + Sync,
{
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
None
}
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
None
}
fn as_stateless(&self) -> &StatelessInteraction<V> {
self
}
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
self
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeyDownEvent {
pub keystroke: Keystroke,
pub is_held: bool,
}
#[derive(Clone, Debug)]
pub struct KeyUpEvent {
pub keystroke: Keystroke,
}
#[derive(Clone, Debug, Default)]
pub struct ModifiersChangedEvent {
pub modifiers: Modifiers,
}
impl Deref for ModifiersChangedEvent {
type Target = Modifiers;
fn deref(&self) -> &Self::Target {
&self.modifiers
}
}
/// The phase of a touch motion event.
/// Based on the winit enum of the same name.
#[derive(Clone, Copy, Debug)]
pub enum TouchPhase {
Started,
Moved,
Ended,
}
#[derive(Clone, Debug, Default)]
pub struct MouseDownEvent {
pub button: MouseButton,
pub position: Point<Pixels>,
pub modifiers: Modifiers,
pub click_count: usize,
}
#[derive(Clone, Debug, Default)]
pub struct MouseUpEvent {
pub button: MouseButton,
pub position: Point<Pixels>,
pub modifiers: Modifiers,
pub click_count: usize,
}
#[derive(Clone, Debug, Default)]
pub struct MouseClickEvent {
pub down: MouseDownEvent,
pub up: MouseUpEvent,
}
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton {
Left,
Right,
Middle,
Navigate(NavigationDirection),
}
impl MouseButton {
pub fn all() -> Vec<Self> {
vec![
MouseButton::Left,
MouseButton::Right,
MouseButton::Middle,
MouseButton::Navigate(NavigationDirection::Back),
MouseButton::Navigate(NavigationDirection::Forward),
]
}
}
impl Default for MouseButton {
fn default() -> Self {
Self::Left
}
}
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum NavigationDirection {
Back,
Forward,
}
impl Default for NavigationDirection {
fn default() -> Self {
Self::Back
}
}
#[derive(Clone, Debug, Default)]
pub struct MouseMoveEvent {
pub position: Point<Pixels>,
pub pressed_button: Option<MouseButton>,
pub modifiers: Modifiers,
}
#[derive(Clone, Debug)]
pub struct ScrollWheelEvent {
pub position: Point<Pixels>,
pub delta: ScrollDelta,
pub modifiers: Modifiers,
pub touch_phase: TouchPhase,
}
impl Deref for ScrollWheelEvent {
type Target = Modifiers;
fn deref(&self) -> &Self::Target {
&self.modifiers
}
}
#[derive(Clone, Copy, Debug)]
pub enum ScrollDelta {
Pixels(Point<Pixels>),
Lines(Point<f32>),
}
impl Default for ScrollDelta {
fn default() -> Self {
Self::Lines(Default::default())
}
}
impl ScrollDelta {
pub fn precise(&self) -> bool {
match self {
ScrollDelta::Pixels(_) => true,
ScrollDelta::Lines(_) => false,
}
}
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
match self {
ScrollDelta::Pixels(delta) => *delta,
ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MouseExitEvent {
pub position: Point<Pixels>,
pub pressed_button: Option<MouseButton>,
pub modifiers: Modifiers,
}
impl Deref for MouseExitEvent {
type Target = Modifiers;
fn deref(&self) -> &Self::Target {
&self.modifiers
}
}
#[derive(Clone, Debug)]
pub enum InputEvent {
KeyDown(KeyDownEvent),
KeyUp(KeyUpEvent),
ModifiersChanged(ModifiersChangedEvent),
MouseDown(MouseDownEvent),
MouseUp(MouseUpEvent),
MouseMoved(MouseMoveEvent),
MouseExited(MouseExitEvent),
ScrollWheel(ScrollWheelEvent),
}
impl InputEvent {
pub fn position(&self) -> Option<Point<Pixels>> {
match self {
InputEvent::KeyDown { .. } => None,
InputEvent::KeyUp { .. } => None,
InputEvent::ModifiersChanged { .. } => None,
InputEvent::MouseDown(event) => Some(event.position),
InputEvent::MouseUp(event) => Some(event.position),
InputEvent::MouseMoved(event) => Some(event.position),
InputEvent::MouseExited(event) => Some(event.position),
InputEvent::ScrollWheel(event) => Some(event.position),
}
}
pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
match self {
InputEvent::KeyDown { .. } => None,
InputEvent::KeyUp { .. } => None,
InputEvent::ModifiersChanged { .. } => None,
InputEvent::MouseDown(event) => Some(event),
InputEvent::MouseUp(event) => Some(event),
InputEvent::MouseMoved(event) => Some(event),
InputEvent::MouseExited(event) => Some(event),
InputEvent::ScrollWheel(event) => Some(event),
}
}
pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
match self {
InputEvent::KeyDown(event) => Some(event),
InputEvent::KeyUp(event) => Some(event),
InputEvent::ModifiersChanged(event) => Some(event),
InputEvent::MouseDown(_) => None,
InputEvent::MouseUp(_) => None,
InputEvent::MouseMoved(_) => None,
InputEvent::MouseExited(_) => None,
InputEvent::ScrollWheel(_) => None,
}
}
}
pub struct FocusEvent {
pub blurred: Option<FocusHandle>,
pub focused: Option<FocusHandle>,
}
pub type MouseDownListener<V> = Arc<
dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+ Send
+ Sync
+ 'static,
>;
pub type MouseUpListener<V> = Arc<
dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+ Send
+ Sync
+ 'static,
>;
pub type MouseClickListener<V> =
Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
pub type MouseMoveListener<V> = Arc<
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+ Send
+ Sync
+ 'static,
>;
pub type ScrollWheelListener<V> = Arc<
dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+ Send
+ Sync
+ 'static,
>;
pub type KeyListener<V> = Arc<
dyn Fn(
&mut V,
&dyn Any,
&[&DispatchContext],
DispatchPhase,
&mut ViewContext<V>,
) -> Option<Box<dyn Action>>
+ Send
+ Sync
+ 'static,
>;
+80
View File
@@ -0,0 +1,80 @@
use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<DispatchContextPredicate>,
}
impl KeyBinding {
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
}
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
Some(DispatchContextPredicate::parse(context)?)
} else {
None
};
let keystrokes = keystrokes
.split_whitespace()
.map(Keystroke::parse)
.collect::<Result<_>>()?;
Ok(Self {
keystrokes,
action,
context_predicate: context,
})
}
pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
.unwrap_or(true)
}
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
contexts: &[&DispatchContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
{
// If the binding is completed, push it onto the matches list
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
KeyMatch::Some(self.action.boxed_clone())
} else {
KeyMatch::Pending
}
} else {
KeyMatch::None
}
}
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[&DispatchContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
} else {
None
}
}
pub fn keystrokes(&self) -> &[Keystroke] {
self.keystrokes.as_slice()
}
pub fn action(&self) -> &dyn Action {
self.action.as_ref()
}
}
+398
View File
@@ -0,0 +1,398 @@
use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
use collections::HashSet;
use smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap};
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct KeymapVersion(usize);
#[derive(Default)]
pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes:
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
version: KeymapVersion,
}
impl Keymap {
pub fn new(bindings: Vec<KeyBinding>) -> Self {
let mut this = Self::default();
this.add_bindings(bindings);
this
}
pub fn version(&self) -> KeymapVersion {
self.version
}
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
self.binding_indices_by_action_id
.get(&action_id)
.map(SmallVec::as_slice)
.unwrap_or(&[])
.iter()
.map(|ix| &self.bindings[*ix])
.filter(|binding| !self.binding_disabled(binding))
}
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
// todo!("no action")
// let no_action_id = (NoAction {}).id();
let mut new_bindings = Vec::new();
let has_new_disabled_keystrokes = false;
for binding in bindings {
// if binding.action().id() == no_action_id {
// has_new_disabled_keystrokes |= self
// .disabled_keystrokes
// .entry(binding.keystrokes)
// .or_default()
// .insert(binding.context_predicate);
// } else {
new_bindings.push(binding);
// }
}
if has_new_disabled_keystrokes {
self.binding_indices_by_action_id.retain(|_, indices| {
indices.retain(|ix| {
let binding = &self.bindings[*ix];
match self.disabled_keystrokes.get(&binding.keystrokes) {
Some(disabled_predicates) => {
!disabled_predicates.contains(&binding.context_predicate)
}
None => true,
}
});
!indices.is_empty()
});
}
for new_binding in new_bindings {
if !self.binding_disabled(&new_binding) {
self.binding_indices_by_action_id
.entry(new_binding.action().as_any().type_id())
.or_default()
.push(self.bindings.len());
self.bindings.push(new_binding);
}
}
self.version.0 += 1;
}
pub fn clear(&mut self) {
self.bindings.clear();
self.binding_indices_by_action_id.clear();
self.disabled_keystrokes.clear();
self.version.0 += 1;
}
pub fn bindings(&self) -> Vec<&KeyBinding> {
self.bindings
.iter()
.filter(|binding| !self.binding_disabled(binding))
.collect()
}
fn binding_disabled(&self, binding: &KeyBinding) -> bool {
match self.disabled_keystrokes.get(&binding.keystrokes) {
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
None => false,
}
}
}
// #[cfg(test)]
// mod tests {
// use crate::actions;
// use super::*;
// actions!(
// keymap_test,
// [Present1, Present2, Present3, Duplicate, Missing]
// );
// #[test]
// fn regular_keymap() {
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let missing = Binding::new("ctrl-r", Missing {}, None);
// let all_bindings = [
// &present_1,
// &present_2,
// &present_3,
// &keystroke_duplicate_to_1,
// &full_duplicate_to_2,
// &missing,
// ];
// let mut keymap = Keymap::default();
// assert_absent(&keymap, &all_bindings);
// assert!(keymap.bindings().is_empty());
// keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
// assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
// assert_present(
// &keymap,
// &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
// );
// keymap.add_bindings([
// keystroke_duplicate_to_1.clone(),
// full_duplicate_to_2.clone(),
// ]);
// assert_absent(&keymap, &[&missing]);
// assert!(
// !keymap.binding_disabled(&keystroke_duplicate_to_1),
// "Duplicate binding 1 was added and should not be disabled"
// );
// assert!(
// !keymap.binding_disabled(&full_duplicate_to_2),
// "Duplicate binding 2 was added and should not be disabled"
// );
// assert_eq!(
// keymap
// .bindings_for_action(keystroke_duplicate_to_1.action().id())
// .map(|binding| &binding.keystrokes)
// .flatten()
// .collect::<Vec<_>>(),
// vec![&Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: "q".to_string(),
// ime_key: None,
// }],
// "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
// );
// assert_eq!(
// keymap
// .bindings_for_action(full_duplicate_to_2.action().id())
// .map(|binding| &binding.keystrokes)
// .flatten()
// .collect::<Vec<_>>(),
// vec![
// &Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: "w".to_string(),
// ime_key: None,
// },
// &Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: "w".to_string(),
// ime_key: None,
// }
// ],
// "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
// );
// let updated_bindings = keymap.bindings();
// let expected_updated_bindings = vec![
// &present_1,
// &present_2,
// &present_3,
// &keystroke_duplicate_to_1,
// &full_duplicate_to_2,
// ];
// assert_eq!(
// updated_bindings.len(),
// expected_updated_bindings.len(),
// "Unexpected updated keymap bindings {updated_bindings:?}"
// );
// for (i, expected) in expected_updated_bindings.iter().enumerate() {
// let keymap_binding = &updated_bindings[i];
// assert_eq!(
// keymap_binding.context_predicate, expected.context_predicate,
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
// );
// assert_eq!(
// keymap_binding.keystrokes, expected.keystrokes,
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
// );
// }
// keymap.clear();
// assert_absent(&keymap, &all_bindings);
// assert!(keymap.bindings().is_empty());
// }
// #[test]
// fn keymap_with_ignored() {
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
// let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
// let ignored_3_with_other_context =
// Binding::new("ctrl-e", NoAction {}, Some("other_context"));
// let mut keymap = Keymap::default();
// keymap.add_bindings([
// ignored_1.clone(),
// ignored_2.clone(),
// ignored_3_with_other_context.clone(),
// ]);
// assert_absent(&keymap, &[&present_3]);
// assert_disabled(
// &keymap,
// &[
// &present_1,
// &present_2,
// &ignored_1,
// &ignored_2,
// &ignored_3_with_other_context,
// ],
// );
// assert!(keymap.bindings().is_empty());
// keymap.clear();
// keymap.add_bindings([
// present_1.clone(),
// present_2.clone(),
// present_3.clone(),
// ignored_1.clone(),
// ignored_2.clone(),
// ignored_3_with_other_context.clone(),
// ]);
// assert_present(&keymap, &[(&present_3, "e")]);
// assert_disabled(
// &keymap,
// &[
// &present_1,
// &present_2,
// &ignored_1,
// &ignored_2,
// &ignored_3_with_other_context,
// ],
// );
// keymap.clear();
// keymap.add_bindings([
// present_1.clone(),
// present_2.clone(),
// present_3.clone(),
// ignored_1.clone(),
// ]);
// assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
// assert_disabled(&keymap, &[&present_1, &ignored_1]);
// assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
// keymap.clear();
// keymap.add_bindings([
// present_1.clone(),
// present_2.clone(),
// present_3.clone(),
// keystroke_duplicate_to_1.clone(),
// full_duplicate_to_2.clone(),
// ignored_1.clone(),
// ignored_2.clone(),
// ignored_3_with_other_context.clone(),
// ]);
// assert_present(&keymap, &[(&present_3, "e")]);
// assert_disabled(
// &keymap,
// &[
// &present_1,
// &present_2,
// &keystroke_duplicate_to_1,
// &full_duplicate_to_2,
// &ignored_1,
// &ignored_2,
// &ignored_3_with_other_context,
// ],
// );
// keymap.clear();
// }
// #[track_caller]
// fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
// let keymap_bindings = keymap.bindings();
// assert_eq!(
// expected_bindings.len(),
// keymap_bindings.len(),
// "Unexpected keymap bindings {keymap_bindings:?}"
// );
// for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
// assert!(
// !keymap.binding_disabled(expected),
// "{expected:?} should not be disabled as it was added into keymap for element {i}"
// );
// assert_eq!(
// keymap
// .bindings_for_action(expected.action().id())
// .map(|binding| &binding.keystrokes)
// .flatten()
// .collect::<Vec<_>>(),
// vec![&Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: expected_key.to_string(),
// ime_key: None,
// }],
// "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
// );
// let keymap_binding = &keymap_bindings[i];
// assert_eq!(
// keymap_binding.context_predicate, expected.context_predicate,
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
// );
// assert_eq!(
// keymap_binding.keystrokes, expected.keystrokes,
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
// );
// }
// }
// #[track_caller]
// fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
// for binding in bindings.iter() {
// assert!(
// !keymap.binding_disabled(binding),
// "{binding:?} should not be disabled in the keymap where was not added"
// );
// assert_eq!(
// keymap.bindings_for_action(binding.action().id()).count(),
// 0,
// "{binding:?} should have no actions in the keymap where was not added"
// );
// }
// }
// #[track_caller]
// fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
// for binding in bindings.iter() {
// assert!(
// keymap.binding_disabled(binding),
// "{binding:?} should be disabled in the keymap"
// );
// assert_eq!(
// keymap.bindings_for_action(binding.action().id()).count(),
// 0,
// "{binding:?} should have no actions in the keymap where it was disabled"
// );
// }
// }
// }
+473
View File
@@ -0,0 +1,473 @@
use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::RwLock;
use smallvec::SmallVec;
use std::sync::Arc;
pub struct KeyMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<RwLock<Keymap>>,
keymap_version: KeymapVersion,
}
impl KeyMatcher {
pub fn new(keymap: Arc<RwLock<Keymap>>) -> Self {
let keymap_version = keymap.read().version();
Self {
pending_keystrokes: Vec::new(),
keymap_version,
keymap,
}
}
// todo!("replace with a function that calls an FnMut for every binding matching the action")
// pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
// self.keymap.read().bindings_for_action(action_id)
// }
pub fn clear_pending(&mut self) {
self.pending_keystrokes.clear();
}
pub fn has_pending_keystrokes(&self) -> bool {
!self.pending_keystrokes.is_empty()
}
/// Pushes a keystroke onto the matcher.
/// The result of the new keystroke is returned:
/// KeyMatch::None =>
/// No match is valid for this key given any pending keystrokes.
/// KeyMatch::Pending =>
/// There exist bindings which are still waiting for more keys.
/// KeyMatch::Complete(matches) =>
/// One or more bindings have received the necessary key presses.
/// Bindings added later will take precedence over earlier bindings.
pub fn match_keystroke(
&mut self,
keystroke: &Keystroke,
context_stack: &[&DispatchContext],
) -> KeyMatch {
let keymap = self.keymap.read();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
if keymap.version() != self.keymap_version {
self.keymap_version = keymap.version();
self.pending_keystrokes.clear();
}
let mut pending_key = None;
for binding in keymap.bindings().iter().rev() {
for candidate in keystroke.match_candidates() {
self.pending_keystrokes.push(candidate.clone());
match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
KeyMatch::Some(action) => {
self.pending_keystrokes.clear();
return KeyMatch::Some(action);
}
KeyMatch::Pending => {
pending_key.get_or_insert(candidate);
}
KeyMatch::None => {}
}
self.pending_keystrokes.pop();
}
}
if let Some(pending_key) = pending_key {
self.pending_keystrokes.push(pending_key);
}
if self.pending_keystrokes.is_empty() {
KeyMatch::None
} else {
KeyMatch::Pending
}
}
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[&DispatchContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.read()
.bindings()
.iter()
.rev()
.find_map(|binding| binding.keystrokes_for_action(action, contexts))
}
}
pub enum KeyMatch {
None,
Pending,
Some(Box<dyn Action>),
}
impl KeyMatch {
pub fn is_some(&self) -> bool {
matches!(self, KeyMatch::Some(_))
}
}
// #[cfg(test)]
// mod tests {
// use anyhow::Result;
// use serde::Deserialize;
// use crate::{actions, impl_actions, keymap_matcher::ActionContext};
// use super::*;
// #[test]
// fn test_keymap_and_view_ordering() -> Result<()> {
// actions!(test, [EditorAction, ProjectPanelAction]);
// let mut editor = ActionContext::default();
// editor.add_identifier("Editor");
// let mut project_panel = ActionContext::default();
// project_panel.add_identifier("ProjectPanel");
// // Editor 'deeper' in than project panel
// let dispatch_path = vec![(2, editor), (1, project_panel)];
// // But editor actions 'higher' up in keymap
// let keymap = Keymap::new(vec![
// Binding::new("left", EditorAction, Some("Editor")),
// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
// ]);
// let mut matcher = KeymapMatcher::new(keymap);
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
// KeyMatch::Matches(vec![
// (2, Box::new(EditorAction)),
// (1, Box::new(ProjectPanelAction)),
// ]),
// );
// Ok(())
// }
// #[test]
// fn test_push_keystroke() -> Result<()> {
// actions!(test, [B, AB, C, D, DA, E, EF]);
// let mut context1 = ActionContext::default();
// context1.add_identifier("1");
// let mut context2 = ActionContext::default();
// context2.add_identifier("2");
// let dispatch_path = vec![(2, context2), (1, context1)];
// let keymap = Keymap::new(vec![
// Binding::new("a b", AB, Some("1")),
// Binding::new("b", B, Some("2")),
// Binding::new("c", C, Some("2")),
// Binding::new("d", D, Some("1")),
// Binding::new("d", D, Some("2")),
// Binding::new("d a", DA, Some("2")),
// ]);
// let mut matcher = KeymapMatcher::new(keymap);
// // Binding with pending prefix always takes precedence
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
// KeyMatch::Pending,
// );
// // B alone doesn't match because a was pending, so AB is returned instead
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
// KeyMatch::Matches(vec![(1, Box::new(AB))]),
// );
// assert!(!matcher.has_pending_keystrokes());
// // Without an a prefix, B is dispatched like expected
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
// KeyMatch::Matches(vec![(2, Box::new(B))]),
// );
// assert!(!matcher.has_pending_keystrokes());
// // If a is prefixed, C will not be dispatched because there
// // was a pending binding for it
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
// KeyMatch::Pending,
// );
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
// KeyMatch::None,
// );
// assert!(!matcher.has_pending_keystrokes());
// // If a single keystroke matches multiple bindings in the tree
// // all of them are returned so that we can fallback if the action
// // handler decides to propagate the action
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
// KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
// );
// // If none of the d action handlers consume the binding, a pending
// // binding may then be used
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
// KeyMatch::Matches(vec![(2, Box::new(DA))]),
// );
// assert!(!matcher.has_pending_keystrokes());
// Ok(())
// }
// #[test]
// fn test_keystroke_parsing() -> Result<()> {
// assert_eq!(
// Keystroke::parse("ctrl-p")?,
// Keystroke {
// key: "p".into(),
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// ime_key: None,
// }
// );
// assert_eq!(
// Keystroke::parse("alt-shift-down")?,
// Keystroke {
// key: "down".into(),
// ctrl: false,
// alt: true,
// shift: true,
// cmd: false,
// function: false,
// ime_key: None,
// }
// );
// assert_eq!(
// Keystroke::parse("shift-cmd--")?,
// Keystroke {
// key: "-".into(),
// ctrl: false,
// alt: false,
// shift: true,
// cmd: true,
// function: false,
// ime_key: None,
// }
// );
// Ok(())
// }
// #[test]
// fn test_context_predicate_parsing() -> Result<()> {
// use KeymapContextPredicate::*;
// assert_eq!(
// KeymapContextPredicate::parse("a && (b == c || d != e)")?,
// And(
// Box::new(Identifier("a".into())),
// Box::new(Or(
// Box::new(Equal("b".into(), "c".into())),
// Box::new(NotEqual("d".into(), "e".into())),
// ))
// )
// );
// assert_eq!(
// KeymapContextPredicate::parse("!a")?,
// Not(Box::new(Identifier("a".into())),)
// );
// Ok(())
// }
// #[test]
// fn test_context_predicate_eval() {
// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
// let mut context = ActionContext::default();
// context.add_identifier("a");
// assert!(!predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_identifier("b");
// assert!(predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_key("c", "x");
// assert!(!predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_key("c", "d");
// assert!(predicate.eval(&[context]));
// let predicate = KeymapContextPredicate::parse("!a").unwrap();
// assert!(predicate.eval(&[ActionContext::default()]));
// }
// #[test]
// fn test_context_child_predicate_eval() {
// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
// let contexts = [
// context_set(&["e", "f"]),
// context_set(&["c", "d"]), // match this context
// context_set(&["a", "b"]),
// ];
// assert!(!predicate.eval(&contexts[0..]));
// assert!(predicate.eval(&contexts[1..]));
// assert!(!predicate.eval(&contexts[2..]));
// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
// let contexts = [
// context_set(&["f"]),
// context_set(&["e"]), // only match this context
// context_set(&["c"]),
// context_set(&["a", "b"]),
// context_set(&["e"]),
// context_set(&["c", "d"]),
// context_set(&["a", "b"]),
// ];
// assert!(!predicate.eval(&contexts[0..]));
// assert!(predicate.eval(&contexts[1..]));
// assert!(!predicate.eval(&contexts[2..]));
// assert!(!predicate.eval(&contexts[3..]));
// assert!(!predicate.eval(&contexts[4..]));
// assert!(!predicate.eval(&contexts[5..]));
// assert!(!predicate.eval(&contexts[6..]));
// fn context_set(names: &[&str]) -> ActionContext {
// let mut keymap = ActionContext::new();
// names
// .iter()
// .for_each(|name| keymap.add_identifier(name.to_string()));
// keymap
// }
// }
// #[test]
// fn test_matcher() -> Result<()> {
// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
// pub struct A(pub String);
// impl_actions!(test, [A]);
// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
// #[derive(Clone, Debug, Eq, PartialEq)]
// struct ActionArg {
// a: &'static str,
// }
// let keymap = Keymap::new(vec![
// Binding::new("a", A("x".to_string()), Some("a")),
// Binding::new("b", B, Some("a")),
// Binding::new("a b", Ab, Some("a || b")),
// Binding::new("$", Dollar, Some("a")),
// Binding::new("\"", Quote, Some("a")),
// Binding::new("alt-s", Ess, Some("a")),
// Binding::new("ctrl-`", Backtick, Some("a")),
// ]);
// let mut context_a = ActionContext::default();
// context_a.add_identifier("a");
// let mut context_b = ActionContext::default();
// context_b.add_identifier("b");
// let mut matcher = KeymapMatcher::new(keymap);
// // Basic match
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
// );
// matcher.clear_pending();
// // Multi-keystroke match
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
// KeyMatch::Pending
// );
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(Ab))])
// );
// matcher.clear_pending();
// // Failed matches don't interfere with matching subsequent keys
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
// KeyMatch::None
// );
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
// );
// matcher.clear_pending();
// // Pending keystrokes are cleared when the context changes
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
// KeyMatch::Pending
// );
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
// KeyMatch::None
// );
// matcher.clear_pending();
// let mut context_c = ActionContext::default();
// context_c.add_identifier("c");
// // Pending keystrokes are maintained per-view
// assert_eq!(
// matcher.match_keystroke(
// Keystroke::parse("a")?,
// vec![(1, context_b.clone()), (2, context_c.clone())]
// ),
// KeyMatch::Pending
// );
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(Ab))])
// );
// // handle Czech $ (option + 4 key)
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(Dollar))])
// );
// // handle Brazillian quote (quote key then space key)
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(Quote))])
// );
// // handle ctrl+` on a brazillian keyboard
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(Backtick))])
// );
// // handle alt-s on a US keyboard
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]),
// KeyMatch::Matches(vec![(1, Box::new(Ess))])
// );
// Ok(())
// }
// }
+7
View File
@@ -0,0 +1,7 @@
mod binding;
mod keymap;
mod matcher;
pub use binding::*;
pub use keymap::*;
pub use matcher::*;
+482
View File
@@ -0,0 +1,482 @@
mod keystroke;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(any(test, feature = "test"))]
mod test;
use crate::{
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun,
GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams,
RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size,
};
use anyhow::anyhow;
use async_task::Runnable;
use futures::channel::oneshot;
use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::{
any::Any,
fmt::{self, Debug, Display},
ops::Range,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
sync::Arc,
};
pub use keystroke::*;
#[cfg(target_os = "macos")]
pub use mac::*;
#[cfg(any(test, feature = "test"))]
pub use test::*;
pub use time::UtcOffset;
#[cfg(target_os = "macos")]
pub(crate) fn current_platform() -> Arc<dyn Platform> {
Arc::new(MacPlatform::new())
}
pub(crate) trait Platform: 'static {
fn executor(&self) -> Executor;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
fn quit(&self);
fn restart(&self);
fn activate(&self, ignoring_other_apps: bool);
fn hide(&self);
fn hide_other_apps(&self);
fn unhide_other_apps(&self);
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn main_window(&self) -> Option<AnyWindowHandle>;
fn open_window(
&self,
handle: AnyWindowHandle,
options: WindowOptions,
) -> Box<dyn PlatformWindow>;
fn set_display_link_output_callback(
&self,
display_id: DisplayId,
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
);
fn start_display_link(&self, display_id: DisplayId);
fn stop_display_link(&self, display_id: DisplayId);
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
fn open_url(&self, url: &str);
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
fn reveal_path(&self, path: &Path);
fn on_become_active(&self, callback: Box<dyn FnMut()>);
fn on_resign_active(&self, callback: Box<dyn FnMut()>);
fn on_quit(&self, callback: Box<dyn FnMut()>);
fn on_reopen(&self, callback: Box<dyn FnMut()>);
fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
fn os_name(&self) -> &'static str;
fn os_version(&self) -> Result<SemanticVersion>;
fn app_version(&self) -> Result<SemanticVersion>;
fn app_path(&self) -> Result<PathBuf>;
fn local_timezone(&self) -> UtcOffset;
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
fn set_cursor_style(&self, style: CursorStyle);
fn should_auto_hide_scrollbars(&self) -> bool;
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
fn delete_credentials(&self, url: &str) -> Result<()>;
}
pub trait PlatformDisplay: Debug {
fn id(&self) -> DisplayId;
fn as_any(&self) -> &dyn Any;
fn bounds(&self) -> Bounds<GlobalPixels>;
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct DisplayId(pub(crate) u32);
impl Debug for DisplayId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DisplayId({})", self.0)
}
}
unsafe impl Send for DisplayId {}
pub(crate) trait PlatformWindow {
fn bounds(&self) -> WindowBounds;
fn content_size(&self) -> Size<Pixels>;
fn scale_factor(&self) -> f32;
fn titlebar_height(&self) -> Pixels;
fn appearance(&self) -> WindowAppearance;
fn display(&self) -> Rc<dyn PlatformDisplay>;
fn mouse_position(&self) -> Point<Pixels>;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
fn prompt(
&self,
level: WindowPromptLevel,
msg: &str,
answers: &[&str],
) -> oneshot::Receiver<usize>;
fn activate(&self);
fn set_title(&mut self, title: &str);
fn set_edited(&mut self, edited: bool);
fn show_character_palette(&self);
fn minimize(&self);
fn zoom(&self);
fn toggle_full_screen(&self);
fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
fn on_moved(&self, callback: Box<dyn FnMut()>);
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
fn on_close(&self, callback: Box<dyn FnOnce()>);
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: Scene);
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
}
pub trait PlatformDispatcher: Send + Sync {
fn is_main_thread(&self) -> bool;
fn dispatch(&self, task: Runnable);
fn dispatch_on_main_thread(&self, task: Runnable);
}
pub trait PlatformTextSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
fn all_font_families(&self) -> Vec<String>;
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize>;
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub enum AtlasKey {
Glyph(RenderGlyphParams),
Svg(RenderSvgParams),
Image(RenderImageParams),
}
impl AtlasKey {
pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
match self {
AtlasKey::Glyph(params) => {
if params.is_emoji {
AtlasTextureKind::Polychrome
} else {
AtlasTextureKind::Monochrome
}
}
AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
}
}
}
impl From<RenderGlyphParams> for AtlasKey {
fn from(params: RenderGlyphParams) -> Self {
Self::Glyph(params)
}
}
impl From<RenderSvgParams> for AtlasKey {
fn from(params: RenderSvgParams) -> Self {
Self::Svg(params)
}
}
impl From<RenderImageParams> for AtlasKey {
fn from(params: RenderImageParams) -> Self {
Self::Image(params)
}
}
pub trait PlatformAtlas: Send + Sync {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
) -> Result<AtlasTile>;
fn clear(&self);
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct AtlasTile {
pub(crate) texture_id: AtlasTextureId,
pub(crate) tile_id: TileId,
pub(crate) bounds: Bounds<DevicePixels>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(C)]
pub(crate) struct AtlasTextureId {
// We use u32 instead of usize for Metal Shader Language compatibility
pub(crate) index: u32,
pub(crate) kind: AtlasTextureKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(C)]
pub(crate) enum AtlasTextureKind {
Monochrome = 0,
Polychrome = 1,
Path = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub(crate) struct TileId(pub(crate) u32);
impl From<etagere::AllocId> for TileId {
fn from(id: etagere::AllocId) -> Self {
Self(id.serialize())
}
}
impl From<TileId> for etagere::AllocId {
fn from(id: TileId) -> Self {
Self::deserialize(id.0)
}
}
pub trait PlatformInputHandler {
fn selected_text_range(&self) -> Option<Range<usize>>;
fn marked_text_range(&self) -> Option<Range<usize>>;
fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
);
fn unmark_text(&mut self);
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
}
#[derive(Debug)]
pub struct WindowOptions {
pub bounds: WindowBounds,
pub titlebar: Option<TitlebarOptions>,
pub center: bool,
pub focus: bool,
pub show: bool,
pub kind: WindowKind,
pub is_movable: bool,
pub display_id: Option<DisplayId>,
}
impl Default for WindowOptions {
fn default() -> Self {
Self {
bounds: WindowBounds::default(),
titlebar: Some(TitlebarOptions {
title: Default::default(),
appears_transparent: Default::default(),
traffic_light_position: Default::default(),
}),
center: false,
focus: true,
show: true,
kind: WindowKind::Normal,
is_movable: true,
display_id: None,
}
}
}
#[derive(Debug, Default)]
pub struct TitlebarOptions {
pub title: Option<SharedString>,
pub appears_transparent: bool,
pub traffic_light_position: Option<Point<Pixels>>,
}
#[derive(Copy, Clone, Debug)]
pub enum Appearance {
Light,
VibrantLight,
Dark,
VibrantDark,
}
impl Default for Appearance {
fn default() -> Self {
Self::Light
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WindowKind {
Normal,
PopUp,
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum WindowBounds {
Fullscreen,
#[default]
Maximized,
Fixed(Bounds<GlobalPixels>),
}
#[derive(Copy, Clone, Debug)]
pub enum WindowAppearance {
Light,
VibrantLight,
Dark,
VibrantDark,
}
impl Default for WindowAppearance {
fn default() -> Self {
Self::Light
}
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum WindowPromptLevel {
#[default]
Info,
Warning,
Critical,
}
#[derive(Copy, Clone, Debug)]
pub struct PathPromptOptions {
pub files: bool,
pub directories: bool,
pub multiple: bool,
}
#[derive(Copy, Clone, Debug)]
pub enum PromptLevel {
Info,
Warning,
Critical,
}
#[derive(Copy, Clone, Debug)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
ResizeUpDown,
PointingHand,
IBeam,
}
impl Default for CursorStyle {
fn default() -> Self {
Self::Arrow
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct SemanticVersion {
major: usize,
minor: usize,
patch: usize,
}
impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut components = s.trim().split('.');
let major = components
.next()
.ok_or_else(|| anyhow!("missing major version number"))?
.parse()?;
let minor = components
.next()
.ok_or_else(|| anyhow!("missing minor version number"))?
.parse()?;
let patch = components
.next()
.ok_or_else(|| anyhow!("missing patch version number"))?
.parse()?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {
pub(crate) text: String,
pub(crate) metadata: Option<String>,
}
impl ClipboardItem {
pub fn new(text: String) -> Self {
Self {
text,
metadata: None,
}
}
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
self
}
pub fn text(&self) -> &String {
&self.text
}
pub fn metadata<T>(&self) -> Option<T>
where
T: for<'a> Deserialize<'a>,
{
self.metadata
.as_ref()
.and_then(|m| serde_json::from_str(m).ok())
}
pub(crate) fn text_hash(text: &str) -> u64 {
let mut hasher = SeaHasher::new();
text.hash(&mut hasher);
hasher.finish()
}
}
+151
View File
@@ -0,0 +1,151 @@
use anyhow::anyhow;
use serde::Deserialize;
use smallvec::SmallVec;
use std::fmt::Write;
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Keystroke {
pub modifiers: Modifiers,
/// key is the character printed on the key that was pressed
/// e.g. for option-s, key is "s"
pub key: String,
/// ime_key is the character inserted by the IME engine when that key was pressed.
/// e.g. for option-s, ime_key is "ß"
pub ime_key: Option<String>,
}
impl Keystroke {
// When matching a key we cannot know whether the user intended to type
// the ime_key or the key. On some non-US keyboards keys we use in our
// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
// and on some keyboards the IME handler converts a sequence of keys into a
// specific character (for example `"` is typed as `" space` on a brazillian keyboard).
pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
let mut possibilities = SmallVec::new();
match self.ime_key.as_ref() {
None => possibilities.push(self.clone()),
Some(ime_key) => {
possibilities.push(Keystroke {
modifiers: Modifiers {
control: self.modifiers.control,
alt: false,
shift: false,
command: false,
function: false,
},
key: ime_key.to_string(),
ime_key: None,
});
possibilities.push(Keystroke {
ime_key: None,
..self.clone()
});
}
}
possibilities
}
/// key syntax is:
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
/// ime_key is only used for generating test events,
/// when matching a key with an ime_key set will be matched without it.
pub fn parse(source: &str) -> anyhow::Result<Self> {
let mut control = false;
let mut alt = false;
let mut shift = false;
let mut command = false;
let mut function = false;
let mut key = None;
let mut ime_key = None;
let mut components = source.split('-').peekable();
while let Some(component) = components.next() {
match component {
"ctrl" => control = true,
"alt" => alt = true,
"shift" => shift = true,
"cmd" => command = true,
"fn" => function = true,
_ => {
if let Some(next) = components.peek() {
if next.is_empty() && source.ends_with('-') {
key = Some(String::from("-"));
break;
} else if next.len() > 1 && next.starts_with('>') {
key = Some(String::from(component));
ime_key = Some(String::from(&next[1..]));
components.next();
} else {
return Err(anyhow!("Invalid keystroke `{}`", source));
}
} else {
key = Some(String::from(component));
}
}
}
}
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
Ok(Keystroke {
modifiers: Modifiers {
control,
alt,
shift,
command,
function,
},
key,
ime_key,
})
}
}
impl std::fmt::Display for Keystroke {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.modifiers.control {
f.write_char('^')?;
}
if self.modifiers.alt {
f.write_char('⌥')?;
}
if self.modifiers.command {
f.write_char('⌘')?;
}
if self.modifiers.shift {
f.write_char('⇧')?;
}
let key = match self.key.as_str() {
"backspace" => '⌫',
"up" => '↑',
"down" => '↓',
"left" => '←',
"right" => '→',
"tab" => '⇥',
"escape" => '⎋',
key => {
if key.len() == 1 {
key.chars().next().unwrap().to_ascii_uppercase()
} else {
return f.write_str(key);
}
}
};
f.write_char(key)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Modifiers {
pub control: bool,
pub alt: bool,
pub shift: bool,
pub command: bool,
pub function: bool,
}
impl Modifiers {
pub fn modified(&self) -> bool {
self.control || self.alt || self.shift || self.command || self.function
}
}
+165
View File
@@ -0,0 +1,165 @@
///! Macos screen have a y axis that goings up from the bottom of the screen and
///! an origin at the bottom left of the main display.
mod dispatcher;
mod display;
mod display_linker;
mod events;
mod metal_atlas;
mod metal_renderer;
mod open_type;
mod platform;
mod text_system;
mod window;
mod window_appearence;
use crate::{px, size, GlobalPixels, Pixels, Size};
use anyhow::anyhow;
use cocoa::{
base::{id, nil},
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger, NSURL},
};
use metal_renderer::*;
use objc::{
msg_send,
runtime::{BOOL, NO, YES},
sel, sel_impl,
};
use std::{
ffi::{c_char, CStr, OsStr},
ops::Range,
os::unix::prelude::OsStrExt,
path::PathBuf,
};
pub use dispatcher::*;
pub use display::*;
pub use display_linker::*;
pub use metal_atlas::*;
pub use platform::*;
pub use text_system::*;
pub use window::*;
trait BoolExt {
fn to_objc(self) -> BOOL;
}
impl BoolExt for bool {
fn to_objc(self) -> BOOL {
if self {
YES
} else {
NO
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
struct NSRange {
pub location: NSUInteger,
pub length: NSUInteger,
}
impl NSRange {
fn invalid() -> Self {
Self {
location: NSNotFound as NSUInteger,
length: 0,
}
}
fn is_valid(&self) -> bool {
self.location != NSNotFound as NSUInteger
}
fn to_range(self) -> Option<Range<usize>> {
if self.is_valid() {
let start = self.location as usize;
let end = start + self.length as usize;
Some(start..end)
} else {
None
}
}
}
impl From<Range<usize>> for NSRange {
fn from(range: Range<usize>) -> Self {
NSRange {
location: range.start as NSUInteger,
length: range.len() as NSUInteger,
}
}
}
unsafe impl objc::Encode for NSRange {
fn encode() -> objc::Encoding {
let encoding = format!(
"{{NSRange={}{}}}",
NSUInteger::encode().as_str(),
NSUInteger::encode().as_str()
);
unsafe { objc::Encoding::from_str(&encoding) }
}
}
unsafe fn ns_string(string: &str) -> id {
NSString::alloc(nil).init_str(string).autorelease()
}
impl From<NSSize> for Size<Pixels> {
fn from(value: NSSize) -> Self {
Size {
width: px(value.width as f32),
height: px(value.height as f32),
}
}
}
pub trait NSRectExt {
fn size(&self) -> Size<Pixels>;
fn intersects(&self, other: Self) -> bool;
}
impl From<NSRect> for Size<Pixels> {
fn from(rect: NSRect) -> Self {
let NSSize { width, height } = rect.size;
size(width.into(), height.into())
}
}
impl From<NSRect> for Size<GlobalPixels> {
fn from(rect: NSRect) -> Self {
let NSSize { width, height } = rect.size;
size(width.into(), height.into())
}
}
// impl NSRectExt for NSRect {
// fn intersects(&self, other: Self) -> bool {
// self.size.width > 0.
// && self.size.height > 0.
// && other.size.width > 0.
// && other.size.height > 0.
// && self.origin.x <= other.origin.x + other.size.width
// && self.origin.x + self.size.width >= other.origin.x
// && self.origin.y <= other.origin.y + other.size.height
// && self.origin.y + self.size.height >= other.origin.y
// }
// }
// todo!
#[allow(unused)]
unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
let path: *mut c_char = msg_send![url, fileSystemRepresentation];
if path.is_null() {
Err(anyhow!(
"url is not a file path: {}",
CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
))
} else {
Ok(PathBuf::from(OsStr::from_bytes(
CStr::from_ptr(path).to_bytes(),
)))
}
}
+1
View File
@@ -0,0 +1 @@
#include <dispatch/dispatch.h>
@@ -0,0 +1,73 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::PlatformDispatcher;
use async_task::Runnable;
use objc::{
class, msg_send,
runtime::{BOOL, YES},
sel, sel_impl,
};
use std::ffi::c_void;
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
pub fn dispatch_get_main_queue() -> dispatch_queue_t {
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
}
pub struct MacDispatcher;
impl PlatformDispatcher for MacDispatcher {
fn is_main_thread(&self) -> bool {
let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
is_main_thread == YES
}
fn dispatch(&self, runnable: Runnable) {
unsafe {
dispatch_async_f(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
runnable.into_raw() as *mut c_void,
Some(trampoline),
);
}
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
unsafe {
dispatch_async_f(
dispatch_get_main_queue(),
runnable.into_raw() as *mut c_void,
Some(trampoline),
);
}
}
}
extern "C" fn trampoline(runnable: *mut c_void) {
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
task.run();
}
// #include <dispatch/dispatch.h>
// int main(void) {
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// // Do some lengthy background work here...
// printf("Background Work\n");
// dispatch_async(dispatch_get_main_queue(), ^{
// // Once done, update your UI on the main queue here.
// printf("UI Updated\n");
// });
// });
// sleep(3); // prevent the program from terminating immediately
// return 0;
// }
// ```
+101
View File
@@ -0,0 +1,101 @@
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
use core_graphics::{
display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
geometry::{CGPoint, CGRect, CGSize},
};
use std::any::Any;
#[derive(Debug)]
pub struct MacDisplay(pub(crate) CGDirectDisplayID);
unsafe impl Send for MacDisplay {}
impl MacDisplay {
/// Get the screen with the given UUID.
pub fn find_by_id(id: DisplayId) -> Option<Self> {
Self::all().find(|screen| screen.id() == id)
}
/// Get the primary screen - the one with the menu bar, and whose bottom left
/// corner is at the origin of the AppKit coordinate system.
pub fn primary() -> Self {
Self::all().next().unwrap()
}
pub fn all() -> impl Iterator<Item = Self> {
unsafe {
let mut display_count: u32 = 0;
let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
if result == 0 {
let mut displays = Vec::with_capacity(display_count as usize);
CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
displays.set_len(display_count as usize);
displays.into_iter().map(|display| MacDisplay(display))
} else {
panic!("Failed to get active display list");
}
}
}
}
/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
///
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
/// with the Y axis pointing upwards.
///
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
/// screen, with the Y axis pointing downwards.
pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
Bounds {
origin: point(
GlobalPixels(rect.origin.x as f32),
GlobalPixels(
primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
),
),
size: size(
GlobalPixels(rect.size.width as f32),
GlobalPixels(rect.size.height as f32),
),
}
}
/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
///
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
/// with the Y axis pointing upwards.
///
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
/// screen, with the Y axis pointing downwards.
pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
let primary_screen_height = MacDisplay::primary().bounds().size.height;
CGRect::new(
&CGPoint::new(
bounds.origin.x.into(),
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
),
&CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
)
}
impl PlatformDisplay for MacDisplay {
fn id(&self) -> DisplayId {
DisplayId(self.0)
}
fn as_any(&self) -> &dyn Any {
self
}
fn bounds(&self) -> Bounds<GlobalPixels> {
unsafe {
let native_bounds = CGDisplayBounds(self.0);
display_bounds_from_native(native_bounds)
}
}
}
@@ -0,0 +1,274 @@
use std::{
ffi::c_void,
mem,
sync::{Arc, Weak},
};
use crate::DisplayId;
use collections::HashMap;
use parking_lot::Mutex;
pub use sys::CVTimeStamp as VideoTimestamp;
pub(crate) struct MacDisplayLinker {
links: HashMap<DisplayId, MacDisplayLink>,
}
struct MacDisplayLink {
system_link: sys::DisplayLink,
_output_callback: Arc<OutputCallback>,
}
impl MacDisplayLinker {
pub fn new() -> Self {
MacDisplayLinker {
links: Default::default(),
}
}
}
type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>;
impl MacDisplayLinker {
pub fn set_output_callback(
&mut self,
display_id: DisplayId,
output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
) {
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
let callback = Arc::new(Mutex::new(output_callback));
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
self.links.insert(
display_id,
MacDisplayLink {
_output_callback: callback,
system_link,
},
);
} else {
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
return;
}
}
pub fn start(&mut self, display_id: DisplayId) {
if let Some(link) = self.links.get_mut(&display_id) {
unsafe {
link.system_link.start();
}
} else {
log::warn!("No DisplayLink callback registered for {:?}", display_id)
}
}
pub fn stop(&mut self, display_id: DisplayId) {
if let Some(link) = self.links.get_mut(&display_id) {
unsafe {
link.system_link.stop();
}
} else {
log::warn!("No DisplayLink callback registered for {:?}", display_id)
}
}
}
unsafe extern "C" fn trampoline(
_display_link_out: *mut sys::CVDisplayLink,
current_time: *const sys::CVTimeStamp,
output_time: *const sys::CVTimeStamp,
_flags_in: i64,
_flags_out: *mut i64,
user_data: *mut c_void,
) -> i32 {
if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
let output_callback: Weak<OutputCallback> =
Weak::from_raw(user_data as *mut OutputCallback);
if let Some(output_callback) = output_callback.upgrade() {
(output_callback.lock())(current_time, output_time)
}
mem::forget(output_callback);
}
0
}
mod sys {
//! Derived from display-link crate under the fololwing license:
//! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
#![allow(dead_code, non_upper_case_globals)]
use foreign_types::{foreign_type, ForeignType};
use std::{
ffi::c_void,
fmt::{Debug, Formatter, Result},
};
#[derive(Debug)]
pub enum CVDisplayLink {}
foreign_type! {
type CType = CVDisplayLink;
fn drop = CVDisplayLinkRelease;
fn clone = CVDisplayLinkRetain;
pub struct DisplayLink;
pub struct DisplayLinkRef;
}
impl Debug for DisplayLink {
fn fmt(&self, formatter: &mut Formatter) -> Result {
formatter
.debug_tuple("DisplayLink")
.field(&self.as_ptr())
.finish()
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CVTimeStamp {
pub version: u32,
pub video_time_scale: i32,
pub video_time: i64,
pub host_time: u64,
pub rate_scalar: f64,
pub video_refresh_period: i64,
pub smpte_time: CVSMPTETime,
pub flags: u64,
pub reserved: u64,
}
pub type CVTimeStampFlags = u64;
pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
kCVTimeStampTopField | kCVTimeStampBottomField;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CVSMPTETime {
pub subframes: i16,
pub subframe_divisor: i16,
pub counter: u32,
pub time_type: u32,
pub flags: u32,
pub hours: i16,
pub minutes: i16,
pub seconds: i16,
pub frames: i16,
}
pub type CVSMPTETimeType = u32;
pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
pub type CVSMPTETimeFlags = u32;
pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
display_link_out: *mut CVDisplayLink,
// A pointer to the current timestamp. This represents the timestamp when the callback is called.
current_time: *const CVTimeStamp,
// A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
output_time: *const CVTimeStamp,
// Unused
flags_in: i64,
// Unused
flags_out: *mut i64,
// A pointer to app-defined data.
display_link_context: *mut c_void,
) -> i32;
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "CoreVideo", kind = "framework")]
#[allow(improper_ctypes)]
extern "C" {
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
display_link_out: *mut *mut CVDisplayLink,
) -> i32;
pub fn CVDisplayLinkCreateWithCGDisplay(
display_id: u32,
display_link_out: *mut *mut CVDisplayLink,
) -> i32;
pub fn CVDisplayLinkSetOutputCallback(
display_link: &mut DisplayLinkRef,
callback: CVDisplayLinkOutputCallback,
user_info: *mut c_void,
) -> i32;
pub fn CVDisplayLinkSetCurrentCGDisplay(
display_link: &mut DisplayLinkRef,
display_id: u32,
) -> i32;
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
}
impl DisplayLink {
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
pub unsafe fn new() -> Option<Self> {
let mut display_link: *mut CVDisplayLink = 0 as _;
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
if code == 0 {
Some(DisplayLink::from_ptr(display_link))
} else {
None
}
}
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
let mut display_link: *mut CVDisplayLink = 0 as _;
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
if code == 0 {
Some(DisplayLink::from_ptr(display_link))
} else {
None
}
}
}
impl DisplayLinkRef {
/// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
pub unsafe fn set_output_callback(
&mut self,
callback: CVDisplayLinkOutputCallback,
user_info: *mut c_void,
) {
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
}
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
pub unsafe fn set_current_display(&mut self, display_id: u32) {
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
}
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
pub unsafe fn start(&mut self) {
assert_eq!(CVDisplayLinkStart(self), 0);
}
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
pub unsafe fn stop(&mut self) {
assert_eq!(CVDisplayLinkStop(self), 0);
}
}
}
+357
View File
@@ -0,0 +1,357 @@
use crate::{
point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
};
use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
base::{id, YES},
foundation::NSString as _,
};
use core_graphics::{
event::{CGEvent, CGEventFlags, CGKeyCode},
event_source::{CGEventSource, CGEventSourceStateID},
};
use ctor::ctor;
use foreign_types::ForeignType;
use objc::{class, msg_send, sel, sel_impl};
use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
const BACKSPACE_KEY: u16 = 0x7f;
const SPACE_KEY: u16 = b' ' as u16;
const ENTER_KEY: u16 = 0x0d;
const NUMPAD_ENTER_KEY: u16 = 0x03;
const ESCAPE_KEY: u16 = 0x1b;
const TAB_KEY: u16 = 0x09;
const SHIFT_TAB_KEY: u16 = 0x19;
static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
#[ctor]
unsafe fn build_event_source() {
let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
EVENT_SOURCE = source.as_ptr();
mem::forget(source);
}
// todo!
#[allow(unused)]
pub fn key_to_native(key: &str) -> Cow<str> {
use cocoa::appkit::*;
let code = match key {
"space" => SPACE_KEY,
"backspace" => BACKSPACE_KEY,
"up" => NSUpArrowFunctionKey,
"down" => NSDownArrowFunctionKey,
"left" => NSLeftArrowFunctionKey,
"right" => NSRightArrowFunctionKey,
"pageup" => NSPageUpFunctionKey,
"pagedown" => NSPageDownFunctionKey,
"home" => NSHomeFunctionKey,
"end" => NSEndFunctionKey,
"delete" => NSDeleteFunctionKey,
"f1" => NSF1FunctionKey,
"f2" => NSF2FunctionKey,
"f3" => NSF3FunctionKey,
"f4" => NSF4FunctionKey,
"f5" => NSF5FunctionKey,
"f6" => NSF6FunctionKey,
"f7" => NSF7FunctionKey,
"f8" => NSF8FunctionKey,
"f9" => NSF9FunctionKey,
"f10" => NSF10FunctionKey,
"f11" => NSF11FunctionKey,
"f12" => NSF12FunctionKey,
_ => return Cow::Borrowed(key),
};
Cow::Owned(String::from_utf16(&[code]).unwrap())
}
unsafe fn read_modifiers(native_event: id) -> Modifiers {
let modifiers = native_event.modifierFlags();
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
Modifiers {
control,
alt,
shift,
command,
function,
}
}
impl InputEvent {
pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
let event_type = native_event.eventType();
// Filter out event types that aren't in the NSEventType enum.
// See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
match event_type as u64 {
0 | 21 | 32 | 33 | 35 | 36 | 37 => {
return None;
}
_ => {}
}
match event_type {
NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
modifiers: read_modifiers(native_event),
})),
NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
keystroke: parse_keystroke(native_event),
is_held: native_event.isARepeat() == YES,
})),
NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
keystroke: parse_keystroke(native_event),
})),
NSEventType::NSLeftMouseDown
| NSEventType::NSRightMouseDown
| NSEventType::NSOtherMouseDown => {
let button = match native_event.buttonNumber() {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| {
Self::MouseDown(MouseDownEvent {
button,
position: point(
px(native_event.locationInWindow().x as f32),
// MacOS screen coordinates are relative to bottom left
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
click_count: native_event.clickCount() as usize,
})
})
}
NSEventType::NSLeftMouseUp
| NSEventType::NSRightMouseUp
| NSEventType::NSOtherMouseUp => {
let button = match native_event.buttonNumber() {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| {
Self::MouseUp(MouseUpEvent {
button,
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
click_count: native_event.clickCount() as usize,
})
})
}
NSEventType::NSScrollWheel => window_height.map(|window_height| {
let phase = match native_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
TouchPhase::Started
}
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
_ => TouchPhase::Moved,
};
let raw_data = point(
native_event.scrollingDeltaX() as f32,
native_event.scrollingDeltaY() as f32,
);
let delta = if native_event.hasPreciseScrollingDeltas() == YES {
ScrollDelta::Pixels(raw_data.map(px))
} else {
ScrollDelta::Lines(raw_data)
};
Self::ScrollWheel(ScrollWheelEvent {
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
delta,
touch_phase: phase,
modifiers: read_modifiers(native_event),
})
}),
NSEventType::NSLeftMouseDragged
| NSEventType::NSRightMouseDragged
| NSEventType::NSOtherMouseDragged => {
let pressed_button = match native_event.buttonNumber() {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| {
Self::MouseMoved(MouseMoveEvent {
pressed_button: Some(pressed_button),
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
})
})
}
NSEventType::NSMouseMoved => window_height.map(|window_height| {
Self::MouseMoved(MouseMoveEvent {
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
pressed_button: None,
modifiers: read_modifiers(native_event),
})
}),
NSEventType::NSMouseExited => window_height.map(|window_height| {
Self::MouseExited(MouseExitEvent {
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
pressed_button: None,
modifiers: read_modifiers(native_event),
})
}),
_ => None,
}
}
}
unsafe fn parse_keystroke(native_event: id) -> Keystroke {
use cocoa::appkit::*;
let mut chars_ignoring_modifiers =
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
.to_str()
.unwrap()
.to_string();
let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
let modifiers = native_event.modifierFlags();
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
&& first_char.map_or(true, |ch| {
!(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
});
#[allow(non_upper_case_globals)]
let key = match first_char {
Some(SPACE_KEY) => "space".to_string(),
Some(BACKSPACE_KEY) => "backspace".to_string(),
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
Some(ESCAPE_KEY) => "escape".to_string(),
Some(TAB_KEY) => "tab".to_string(),
Some(SHIFT_TAB_KEY) => "tab".to_string(),
Some(NSUpArrowFunctionKey) => "up".to_string(),
Some(NSDownArrowFunctionKey) => "down".to_string(),
Some(NSLeftArrowFunctionKey) => "left".to_string(),
Some(NSRightArrowFunctionKey) => "right".to_string(),
Some(NSPageUpFunctionKey) => "pageup".to_string(),
Some(NSPageDownFunctionKey) => "pagedown".to_string(),
Some(NSHomeFunctionKey) => "home".to_string(),
Some(NSEndFunctionKey) => "end".to_string(),
Some(NSDeleteFunctionKey) => "delete".to_string(),
Some(NSF1FunctionKey) => "f1".to_string(),
Some(NSF2FunctionKey) => "f2".to_string(),
Some(NSF3FunctionKey) => "f3".to_string(),
Some(NSF4FunctionKey) => "f4".to_string(),
Some(NSF5FunctionKey) => "f5".to_string(),
Some(NSF6FunctionKey) => "f6".to_string(),
Some(NSF7FunctionKey) => "f7".to_string(),
Some(NSF8FunctionKey) => "f8".to_string(),
Some(NSF9FunctionKey) => "f9".to_string(),
Some(NSF10FunctionKey) => "f10".to_string(),
Some(NSF11FunctionKey) => "f11".to_string(),
Some(NSF12FunctionKey) => "f12".to_string(),
_ => {
let mut chars_ignoring_modifiers_and_shift =
chars_for_modified_key(native_event.keyCode(), false, false);
// Honor ⌘ when Dvorak-QWERTY is used.
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
chars_ignoring_modifiers =
chars_for_modified_key(native_event.keyCode(), true, shift);
chars_ignoring_modifiers_and_shift = chars_with_cmd;
}
if shift {
if chars_ignoring_modifiers_and_shift
== chars_ignoring_modifiers.to_ascii_lowercase()
{
chars_ignoring_modifiers_and_shift
} else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
shift = false;
chars_ignoring_modifiers
} else {
chars_ignoring_modifiers
}
} else {
chars_ignoring_modifiers
}
}
};
Keystroke {
modifiers: Modifiers {
control,
alt,
shift,
command,
function,
},
key,
ime_key: None,
}
}
fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
// Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
// always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
// an event with the given flags instead lets us access `characters`, which always
// returns a valid string.
let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
mem::forget(source);
let mut flags = CGEventFlags::empty();
if cmd {
flags |= CGEventFlags::CGEventFlagCommand;
}
if shift {
flags |= CGEventFlags::CGEventFlagShift;
}
event.set_flags(flags);
unsafe {
let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
CStr::from_ptr(event.characters().UTF8String())
.to_str()
.unwrap()
.to_string()
}
}
@@ -0,0 +1,256 @@
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size,
};
use anyhow::Result;
use collections::HashMap;
use derive_more::{Deref, DerefMut};
use etagere::BucketedAtlasAllocator;
use metal::Device;
use parking_lot::Mutex;
use std::borrow::Cow;
pub struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas {
pub fn new(device: Device) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
path_textures: Default::default(),
tiles_by_key: Default::default(),
}))
}
pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
self.0.lock().texture(id).metal_texture.clone()
}
pub(crate) fn allocate(
&self,
size: Size<DevicePixels>,
texture_kind: AtlasTextureKind,
) -> AtlasTile {
self.0.lock().allocate(size, texture_kind)
}
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock();
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
AtlasTextureKind::Path => &mut lock.path_textures,
};
for texture in textures {
texture.clear();
}
}
}
struct MetalAtlasState {
device: AssertSend<Device>,
monochrome_textures: Vec<MetalAtlasTexture>,
polychrome_textures: Vec<MetalAtlasTexture>,
path_textures: Vec<MetalAtlasTexture>,
tiles_by_key: HashMap<AtlasKey, AtlasTile>,
}
impl PlatformAtlas for MetalAtlas {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
) -> Result<AtlasTile> {
let mut lock = self.0.lock();
if let Some(tile) = lock.tiles_by_key.get(key) {
return Ok(tile.clone());
} else {
let (size, bytes) = build()?;
let tile = lock.allocate(size, key.texture_kind());
let texture = lock.texture(tile.texture_id);
texture.upload(tile.bounds, &bytes);
lock.tiles_by_key.insert(key.clone(), tile.clone());
Ok(tile)
}
}
fn clear(&self) {
let mut lock = self.0.lock();
lock.tiles_by_key.clear();
for texture in &mut lock.monochrome_textures {
texture.clear();
}
for texture in &mut lock.polychrome_textures {
texture.clear();
}
for texture in &mut lock.path_textures {
texture.clear();
}
}
}
impl MetalAtlasState {
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
.unwrap_or_else(|| {
let texture = self.push_texture(size, texture_kind);
texture.allocate(size).unwrap()
})
}
fn push_texture(
&mut self,
min_size: Size<DevicePixels>,
kind: AtlasTextureKind,
) -> &mut MetalAtlasTexture {
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(1024),
height: DevicePixels(1024),
};
let size = min_size.max(&DEFAULT_ATLAS_SIZE);
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_width(size.width.into());
texture_descriptor.set_height(size.height.into());
let pixel_format;
let usage;
match kind {
AtlasTextureKind::Monochrome => {
pixel_format = metal::MTLPixelFormat::A8Unorm;
usage = metal::MTLTextureUsage::ShaderRead;
}
AtlasTextureKind::Polychrome => {
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
usage = metal::MTLTextureUsage::ShaderRead;
}
AtlasTextureKind::Path => {
pixel_format = metal::MTLPixelFormat::R16Float;
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
}
}
texture_descriptor.set_pixel_format(pixel_format);
texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor);
let textures = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
let atlas_texture = MetalAtlasTexture {
id: AtlasTextureId {
index: textures.len() as u32,
kind,
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture),
};
textures.push(atlas_texture);
textures.last_mut().unwrap()
}
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
};
&textures[id.index as usize]
}
}
struct MetalAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>,
}
impl MetalAtlasTexture {
fn clear(&mut self) {
self.allocator.clear();
}
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
texture_id: self.id,
tile_id: allocation.id.into(),
bounds: Bounds {
origin: allocation.rectangle.min.into(),
size,
},
};
Some(tile)
}
fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
let region = metal::MTLRegion::new_2d(
bounds.origin.x.into(),
bounds.origin.y.into(),
bounds.size.width.into(),
bounds.size.height.into(),
);
self.metal_texture.replace_region(
region,
0,
bytes.as_ptr() as *const _,
u32::from(bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
);
}
fn bytes_per_pixel(&self) -> u8 {
use metal::MTLPixelFormat::*;
match self.metal_texture.pixel_format() {
A8Unorm | R8Unorm => 1,
RGBA8Unorm | BGRA8Unorm => 4,
_ => unimplemented!(),
}
}
}
impl From<Size<DevicePixels>> for etagere::Size {
fn from(size: Size<DevicePixels>) -> Self {
etagere::Size::new(size.width.into(), size.height.into())
}
}
impl From<etagere::Point> for Point<DevicePixels> {
fn from(value: etagere::Point) -> Self {
Point {
x: DevicePixels::from(value.x),
y: DevicePixels::from(value.y),
}
}
}
impl From<etagere::Size> for Size<DevicePixels> {
fn from(size: etagere::Size) -> Self {
Size {
width: DevicePixels::from(size.width),
height: DevicePixels::from(size.height),
}
}
}
impl From<etagere::Rectangle> for Bounds<DevicePixels> {
fn from(rectangle: etagere::Rectangle) -> Self {
Bounds {
origin: rectangle.min.into(),
size: rectangle.size().into(),
}
}
}
#[derive(Deref, DerefMut)]
struct AssertSend<T>(T);
unsafe impl<T> Send for AssertSend<T> {}
@@ -0,0 +1,880 @@
use crate::{
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
Quad, ScaledPixels, Scene, Shadow, Size, Underline,
};
use cocoa::{
base::{NO, YES},
foundation::NSUInteger,
quartzcore::AutoresizingMask,
};
use collections::HashMap;
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
use smallvec::SmallVec;
use std::{ffi::c_void, mem, ptr, sync::Arc};
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
pub(crate) struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
paths_rasterization_pipeline_state: metal::RenderPipelineState,
path_sprites_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
sprite_atlas: Arc<MetalAtlas>,
}
impl MetalRenderer {
pub fn new(is_opaque: bool) -> Self {
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
device
} else {
log::error!("unable to access a compatible graphics device");
std::process::exit(1);
};
let layer = metal::MetalLayer::new();
layer.set_device(&device);
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
layer.set_presents_with_transaction(true);
layer.set_opaque(is_opaque);
unsafe {
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
let _: () = msg_send![
&*layer,
setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
| AutoresizingMask::HEIGHT_SIZABLE
];
}
let library = device
.new_library_with_data(SHADERS_METALLIB)
.expect("error building metal library");
fn to_float2_bits(point: crate::PointF) -> u64 {
unsafe {
let mut output = mem::transmute::<_, u32>(point.y.to_bits()) as u64;
output <<= 32;
output |= mem::transmute::<_, u32>(point.x.to_bits()) as u64;
output
}
}
let unit_vertices = [
to_float2_bits(point(0., 0.)),
to_float2_bits(point(1., 0.)),
to_float2_bits(point(0., 1.)),
to_float2_bits(point(0., 1.)),
to_float2_bits(point(1., 0.)),
to_float2_bits(point(1., 1.)),
];
let unit_vertices = device.new_buffer_with_data(
unit_vertices.as_ptr() as *const c_void,
(unit_vertices.len() * mem::size_of::<u64>()) as u64,
MTLResourceOptions::StorageModeManaged,
);
let instances = device.new_buffer(
INSTANCE_BUFFER_SIZE as u64,
MTLResourceOptions::StorageModeManaged,
);
let paths_rasterization_pipeline_state = build_pipeline_state(
&device,
&library,
"paths_rasterization",
"path_rasterization_vertex",
"path_rasterization_fragment",
MTLPixelFormat::R16Float,
);
let path_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
"path_sprites",
"path_sprite_vertex",
"path_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let shadows_pipeline_state = build_pipeline_state(
&device,
&library,
"shadows",
"shadow_vertex",
"shadow_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let quads_pipeline_state = build_pipeline_state(
&device,
&library,
"quads",
"quad_vertex",
"quad_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let underlines_pipeline_state = build_pipeline_state(
&device,
&library,
"underlines",
"underline_vertex",
"underline_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let monochrome_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
"monochrome_sprites",
"monochrome_sprite_vertex",
"monochrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let polychrome_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
"polychrome_sprites",
"polychrome_sprite_vertex",
"polychrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let command_queue = device.new_command_queue();
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
Self {
layer,
command_queue,
paths_rasterization_pipeline_state,
path_sprites_pipeline_state,
shadows_pipeline_state,
quads_pipeline_state,
underlines_pipeline_state,
monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
unit_vertices,
instances,
sprite_atlas,
}
}
pub fn layer(&self) -> &metal::MetalLayerRef {
&*self.layer
}
pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
&self.sprite_atlas
}
pub fn draw(&mut self, scene: &Scene) {
let layer = self.layer.clone();
let viewport_size = layer.drawable_size();
let viewport_size: Size<DevicePixels> = size(
(viewport_size.width.ceil() as i32).into(),
(viewport_size.height.ceil() as i32).into(),
);
let drawable = if let Some(drawable) = layer.next_drawable() {
drawable
} else {
log::error!(
"failed to retrieve next drawable, drawable size: {:?}",
viewport_size
);
return;
};
let command_queue = self.command_queue.clone();
let command_buffer = command_queue.new_command_buffer();
let mut instance_offset = 0;
let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
color_attachment.set_texture(Some(drawable.texture()));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_viewport(metal::MTLViewport {
originX: 0.0,
originY: 0.0,
width: i32::from(viewport_size.width) as f64,
height: i32::from(viewport_size.height) as f64,
znear: 0.0,
zfar: 1.0,
});
for batch in scene.batches() {
match batch {
PrimitiveBatch::Shadows(shadows) => {
self.draw_shadows(
shadows,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::Quads(quads) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
}
PrimitiveBatch::Paths(paths) => {
self.draw_paths(
paths,
&path_tiles,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::Underlines(underlines) => {
self.draw_underlines(
underlines,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::MonochromeSprites {
texture_id,
sprites,
} => {
self.draw_monochrome_sprites(
texture_id,
sprites,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::PolychromeSprites {
texture_id,
sprites,
} => {
self.draw_polychrome_sprites(
texture_id,
sprites,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
}
}
command_encoder.end_encoding();
self.instances.did_modify_range(NSRange {
location: 0,
length: instance_offset as NSUInteger,
});
command_buffer.commit();
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
command_buffer.wait_until_completed();
drawable.present();
}
fn rasterize_paths(
&mut self,
paths: &[Path<ScaledPixels>],
offset: &mut usize,
command_buffer: &metal::CommandBufferRef,
) -> HashMap<PathId, AtlasTile> {
let mut tiles = HashMap::default();
let mut vertices_by_texture_id = HashMap::default();
for path in paths {
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
let tile = self
.sprite_atlas
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
vertices_by_texture_id
.entry(tile.texture_id)
.or_insert(Vec::new())
.extend(path.vertices.iter().map(|vertex| PathVertex {
xy_position: vertex.xy_position - path.bounds.origin
+ tile.bounds.origin.map(Into::into),
st_position: vertex.st_position,
content_mask: ContentMask {
bounds: tile.bounds.map(Into::into),
},
}));
tiles.insert(path.id, tile);
}
for (texture_id, vertices) in vertices_by_texture_id {
align_offset(offset);
let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
let texture = self.sprite_atlas.metal_texture(texture_id);
color_attachment.set_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
command_encoder.set_vertex_buffer(
PathRasterizationInputIndex::Vertices as u64,
Some(&self.instances),
*offset as u64,
);
let texture_size = Size {
width: DevicePixels::from(texture.width()),
height: DevicePixels::from(texture.height()),
};
command_encoder.set_vertex_bytes(
PathRasterizationInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
vertices.as_ptr() as *const u8,
buffer_contents,
vertices_bytes_len,
);
}
command_encoder.draw_primitives(
metal::MTLPrimitiveType::Triangle,
0,
vertices.len() as u64,
);
command_encoder.end_encoding();
*offset = next_offset;
}
tiles
}
fn draw_shadows(
&mut self,
shadows: &[Shadow],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if shadows.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
command_encoder.set_vertex_buffer(
ShadowInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
ShadowInputIndex::Shadows as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
ShadowInputIndex::Shadows as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
ShadowInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
shadows.as_ptr() as *const u8,
buffer_contents,
shadow_bytes_len,
);
}
let next_offset = *offset + shadow_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
shadows.len() as u64,
);
*offset = next_offset;
}
fn draw_quads(
&mut self,
quads: &[Quad],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if quads.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
command_encoder.set_vertex_buffer(
QuadInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
QuadInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quads.len() as u64,
);
*offset = next_offset;
}
fn draw_paths(
&mut self,
paths: &[Path<ScaledPixels>],
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if paths.is_empty() {
return;
}
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let mut prev_texture_id = None;
let mut sprites = SmallVec::<[_; 1]>::new();
let mut paths_and_tiles = paths
.into_iter()
.map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
.peekable();
loop {
if let Some((path, tile)) = paths_and_tiles.peek() {
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
prev_texture_id = Some(tile.texture_id);
sprites.push(PathSprite {
bounds: Bounds {
origin: path.bounds.origin.map(|p| p.floor()),
size: tile.bounds.size.map(Into::into),
},
color: path.color,
tile: (*tile).clone(),
});
paths_and_tiles.next();
continue;
}
}
if sprites.is_empty() {
break;
} else {
align_offset(offset);
let texture_id = prev_texture_id.take().unwrap();
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
let texture_size = size(
DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32),
);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
command_encoder.set_fragment_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
let buffer_contents =
unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
sprites.as_ptr() as *const u8,
buffer_contents,
sprite_bytes_len,
);
}
let next_offset = *offset + sprite_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprites.len() as u64,
);
*offset = next_offset;
sprites.clear();
}
}
}
fn draw_underlines(
&mut self,
underlines: &[Underline],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if underlines.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
UnderlineInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Underline>() * underlines.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
underlines.as_ptr() as *const u8,
buffer_contents,
quad_bytes_len,
);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
underlines.len() as u64,
);
*offset = next_offset;
}
fn draw_monochrome_sprites(
&mut self,
texture_id: AtlasTextureId,
sprites: &[MonochromeSprite],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if sprites.is_empty() {
return;
}
align_offset(offset);
let texture = self.sprite_atlas.metal_texture(texture_id);
let texture_size = size(
DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32),
);
command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
command_encoder.set_fragment_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
sprites.as_ptr() as *const u8,
buffer_contents,
sprite_bytes_len,
);
}
let next_offset = *offset + sprite_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprites.len() as u64,
);
*offset = next_offset;
}
fn draw_polychrome_sprites(
&mut self,
texture_id: AtlasTextureId,
sprites: &[PolychromeSprite],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if sprites.is_empty() {
return;
}
align_offset(offset);
let texture = self.sprite_atlas.metal_texture(texture_id);
let texture_size = size(
DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32),
);
command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
command_encoder.set_fragment_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
let sprite_bytes_len = mem::size_of::<PolychromeSprite>() * sprites.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
sprites.as_ptr() as *const u8,
buffer_contents,
sprite_bytes_len,
);
}
let next_offset = *offset + sprite_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprites.len() as u64,
);
*offset = next_offset;
}
}
fn build_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
label: &str,
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
.expect("error locating vertex function");
let fragment_fn = library
.get_function(fragment_fn_name, None)
.expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
device
.new_render_pipeline_state(&descriptor)
.expect("could not create render pipeline state")
}
// Align to multiples of 256 make Metal happy.
fn align_offset(offset: &mut usize) {
*offset = ((*offset + 255) / 256) * 256;
}
#[repr(C)]
enum ShadowInputIndex {
Vertices = 0,
Shadows = 1,
ViewportSize = 2,
}
#[repr(C)]
enum QuadInputIndex {
Vertices = 0,
Quads = 1,
ViewportSize = 2,
}
#[repr(C)]
enum UnderlineInputIndex {
Vertices = 0,
Underlines = 1,
ViewportSize = 2,
}
#[repr(C)]
enum SpriteInputIndex {
Vertices = 0,
Sprites = 1,
ViewportSize = 2,
AtlasTextureSize = 3,
AtlasTexture = 4,
}
#[repr(C)]
enum PathRasterizationInputIndex {
Vertices = 0,
AtlasTextureSize = 1,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct PathSprite {
pub bounds: Bounds<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
}
+394
View File
@@ -0,0 +1,394 @@
#![allow(unused, non_upper_case_globals)]
use crate::FontFeatures;
use cocoa::appkit::CGFloat;
use core_foundation::{base::TCFType, number::CFNumber};
use core_graphics::geometry::CGAffineTransform;
use core_text::{
font::{CTFont, CTFontRef},
font_descriptor::{
CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
},
};
use font_kit::font::Font;
use std::ptr;
const kCaseSensitiveLayoutOffSelector: i32 = 1;
const kCaseSensitiveLayoutOnSelector: i32 = 0;
const kCaseSensitiveLayoutType: i32 = 33;
const kCaseSensitiveSpacingOffSelector: i32 = 3;
const kCaseSensitiveSpacingOnSelector: i32 = 2;
const kCharacterAlternativesType: i32 = 17;
const kCommonLigaturesOffSelector: i32 = 3;
const kCommonLigaturesOnSelector: i32 = 2;
const kContextualAlternatesOffSelector: i32 = 1;
const kContextualAlternatesOnSelector: i32 = 0;
const kContextualAlternatesType: i32 = 36;
const kContextualLigaturesOffSelector: i32 = 19;
const kContextualLigaturesOnSelector: i32 = 18;
const kContextualSwashAlternatesOffSelector: i32 = 5;
const kContextualSwashAlternatesOnSelector: i32 = 4;
const kDefaultLowerCaseSelector: i32 = 0;
const kDefaultUpperCaseSelector: i32 = 0;
const kDiagonalFractionsSelector: i32 = 2;
const kFractionsType: i32 = 11;
const kHistoricalLigaturesOffSelector: i32 = 21;
const kHistoricalLigaturesOnSelector: i32 = 20;
const kHojoCharactersSelector: i32 = 12;
const kInferiorsSelector: i32 = 2;
const kJIS2004CharactersSelector: i32 = 11;
const kLigaturesType: i32 = 1;
const kLowerCasePetiteCapsSelector: i32 = 2;
const kLowerCaseSmallCapsSelector: i32 = 1;
const kLowerCaseType: i32 = 37;
const kLowerCaseNumbersSelector: i32 = 0;
const kMathematicalGreekOffSelector: i32 = 11;
const kMathematicalGreekOnSelector: i32 = 10;
const kMonospacedNumbersSelector: i32 = 0;
const kNLCCharactersSelector: i32 = 13;
const kNoFractionsSelector: i32 = 0;
const kNormalPositionSelector: i32 = 0;
const kNoStyleOptionsSelector: i32 = 0;
const kNumberCaseType: i32 = 21;
const kNumberSpacingType: i32 = 6;
const kOrdinalsSelector: i32 = 3;
const kProportionalNumbersSelector: i32 = 1;
const kQuarterWidthTextSelector: i32 = 4;
const kScientificInferiorsSelector: i32 = 4;
const kSlashedZeroOffSelector: i32 = 5;
const kSlashedZeroOnSelector: i32 = 4;
const kStyleOptionsType: i32 = 19;
const kStylisticAltEighteenOffSelector: i32 = 37;
const kStylisticAltEighteenOnSelector: i32 = 36;
const kStylisticAltEightOffSelector: i32 = 17;
const kStylisticAltEightOnSelector: i32 = 16;
const kStylisticAltElevenOffSelector: i32 = 23;
const kStylisticAltElevenOnSelector: i32 = 22;
const kStylisticAlternativesType: i32 = 35;
const kStylisticAltFifteenOffSelector: i32 = 31;
const kStylisticAltFifteenOnSelector: i32 = 30;
const kStylisticAltFiveOffSelector: i32 = 11;
const kStylisticAltFiveOnSelector: i32 = 10;
const kStylisticAltFourOffSelector: i32 = 9;
const kStylisticAltFourOnSelector: i32 = 8;
const kStylisticAltFourteenOffSelector: i32 = 29;
const kStylisticAltFourteenOnSelector: i32 = 28;
const kStylisticAltNineOffSelector: i32 = 19;
const kStylisticAltNineOnSelector: i32 = 18;
const kStylisticAltNineteenOffSelector: i32 = 39;
const kStylisticAltNineteenOnSelector: i32 = 38;
const kStylisticAltOneOffSelector: i32 = 3;
const kStylisticAltOneOnSelector: i32 = 2;
const kStylisticAltSevenOffSelector: i32 = 15;
const kStylisticAltSevenOnSelector: i32 = 14;
const kStylisticAltSeventeenOffSelector: i32 = 35;
const kStylisticAltSeventeenOnSelector: i32 = 34;
const kStylisticAltSixOffSelector: i32 = 13;
const kStylisticAltSixOnSelector: i32 = 12;
const kStylisticAltSixteenOffSelector: i32 = 33;
const kStylisticAltSixteenOnSelector: i32 = 32;
const kStylisticAltTenOffSelector: i32 = 21;
const kStylisticAltTenOnSelector: i32 = 20;
const kStylisticAltThirteenOffSelector: i32 = 27;
const kStylisticAltThirteenOnSelector: i32 = 26;
const kStylisticAltThreeOffSelector: i32 = 7;
const kStylisticAltThreeOnSelector: i32 = 6;
const kStylisticAltTwelveOffSelector: i32 = 25;
const kStylisticAltTwelveOnSelector: i32 = 24;
const kStylisticAltTwentyOffSelector: i32 = 41;
const kStylisticAltTwentyOnSelector: i32 = 40;
const kStylisticAltTwoOffSelector: i32 = 5;
const kStylisticAltTwoOnSelector: i32 = 4;
const kSuperiorsSelector: i32 = 1;
const kSwashAlternatesOffSelector: i32 = 3;
const kSwashAlternatesOnSelector: i32 = 2;
const kTitlingCapsSelector: i32 = 4;
const kTypographicExtrasType: i32 = 14;
const kVerticalFractionsSelector: i32 = 1;
const kVerticalPositionType: i32 = 10;
pub fn apply_features(font: &mut Font, features: FontFeatures) {
// See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
// for a reference implementation.
toggle_open_type_feature(
font,
features.calt(),
kContextualAlternatesType,
kContextualAlternatesOnSelector,
kContextualAlternatesOffSelector,
);
toggle_open_type_feature(
font,
features.case(),
kCaseSensitiveLayoutType,
kCaseSensitiveLayoutOnSelector,
kCaseSensitiveLayoutOffSelector,
);
toggle_open_type_feature(
font,
features.cpsp(),
kCaseSensitiveLayoutType,
kCaseSensitiveSpacingOnSelector,
kCaseSensitiveSpacingOffSelector,
);
toggle_open_type_feature(
font,
features.frac(),
kFractionsType,
kDiagonalFractionsSelector,
kNoFractionsSelector,
);
toggle_open_type_feature(
font,
features.liga(),
kLigaturesType,
kCommonLigaturesOnSelector,
kCommonLigaturesOffSelector,
);
toggle_open_type_feature(
font,
features.onum(),
kNumberCaseType,
kLowerCaseNumbersSelector,
2,
);
toggle_open_type_feature(
font,
features.ordn(),
kVerticalPositionType,
kOrdinalsSelector,
kNormalPositionSelector,
);
toggle_open_type_feature(
font,
features.pnum(),
kNumberSpacingType,
kProportionalNumbersSelector,
4,
);
toggle_open_type_feature(
font,
features.ss01(),
kStylisticAlternativesType,
kStylisticAltOneOnSelector,
kStylisticAltOneOffSelector,
);
toggle_open_type_feature(
font,
features.ss02(),
kStylisticAlternativesType,
kStylisticAltTwoOnSelector,
kStylisticAltTwoOffSelector,
);
toggle_open_type_feature(
font,
features.ss03(),
kStylisticAlternativesType,
kStylisticAltThreeOnSelector,
kStylisticAltThreeOffSelector,
);
toggle_open_type_feature(
font,
features.ss04(),
kStylisticAlternativesType,
kStylisticAltFourOnSelector,
kStylisticAltFourOffSelector,
);
toggle_open_type_feature(
font,
features.ss05(),
kStylisticAlternativesType,
kStylisticAltFiveOnSelector,
kStylisticAltFiveOffSelector,
);
toggle_open_type_feature(
font,
features.ss06(),
kStylisticAlternativesType,
kStylisticAltSixOnSelector,
kStylisticAltSixOffSelector,
);
toggle_open_type_feature(
font,
features.ss07(),
kStylisticAlternativesType,
kStylisticAltSevenOnSelector,
kStylisticAltSevenOffSelector,
);
toggle_open_type_feature(
font,
features.ss08(),
kStylisticAlternativesType,
kStylisticAltEightOnSelector,
kStylisticAltEightOffSelector,
);
toggle_open_type_feature(
font,
features.ss09(),
kStylisticAlternativesType,
kStylisticAltNineOnSelector,
kStylisticAltNineOffSelector,
);
toggle_open_type_feature(
font,
features.ss10(),
kStylisticAlternativesType,
kStylisticAltTenOnSelector,
kStylisticAltTenOffSelector,
);
toggle_open_type_feature(
font,
features.ss11(),
kStylisticAlternativesType,
kStylisticAltElevenOnSelector,
kStylisticAltElevenOffSelector,
);
toggle_open_type_feature(
font,
features.ss12(),
kStylisticAlternativesType,
kStylisticAltTwelveOnSelector,
kStylisticAltTwelveOffSelector,
);
toggle_open_type_feature(
font,
features.ss13(),
kStylisticAlternativesType,
kStylisticAltThirteenOnSelector,
kStylisticAltThirteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss14(),
kStylisticAlternativesType,
kStylisticAltFourteenOnSelector,
kStylisticAltFourteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss15(),
kStylisticAlternativesType,
kStylisticAltFifteenOnSelector,
kStylisticAltFifteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss16(),
kStylisticAlternativesType,
kStylisticAltSixteenOnSelector,
kStylisticAltSixteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss17(),
kStylisticAlternativesType,
kStylisticAltSeventeenOnSelector,
kStylisticAltSeventeenOffSelector,
);
toggle_open_type_feature(
font,
features.ss18(),
kStylisticAlternativesType,
kStylisticAltEighteenOnSelector,
kStylisticAltEighteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss19(),
kStylisticAlternativesType,
kStylisticAltNineteenOnSelector,
kStylisticAltNineteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss20(),
kStylisticAlternativesType,
kStylisticAltTwentyOnSelector,
kStylisticAltTwentyOffSelector,
);
toggle_open_type_feature(
font,
features.subs(),
kVerticalPositionType,
kInferiorsSelector,
kNormalPositionSelector,
);
toggle_open_type_feature(
font,
features.sups(),
kVerticalPositionType,
kSuperiorsSelector,
kNormalPositionSelector,
);
toggle_open_type_feature(
font,
features.swsh(),
kContextualAlternatesType,
kSwashAlternatesOnSelector,
kSwashAlternatesOffSelector,
);
toggle_open_type_feature(
font,
features.titl(),
kStyleOptionsType,
kTitlingCapsSelector,
kNoStyleOptionsSelector,
);
toggle_open_type_feature(
font,
features.tnum(),
kNumberSpacingType,
kMonospacedNumbersSelector,
4,
);
toggle_open_type_feature(
font,
features.zero(),
kTypographicExtrasType,
kSlashedZeroOnSelector,
kSlashedZeroOffSelector,
);
}
fn toggle_open_type_feature(
font: &mut Font,
enabled: Option<bool>,
type_identifier: i32,
on_selector_identifier: i32,
off_selector_identifier: i32,
) {
if let Some(enabled) = enabled {
let native_font = font.native_font();
unsafe {
let selector_identifier = if enabled {
on_selector_identifier
} else {
off_selector_identifier
};
let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
native_font.copy_descriptor().as_concrete_TypeRef(),
CFNumber::from(type_identifier).as_concrete_TypeRef(),
CFNumber::from(selector_identifier).as_concrete_TypeRef(),
);
let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
let new_font = CTFontCreateCopyWithAttributes(
font.native_font().as_concrete_TypeRef(),
0.0,
ptr::null(),
new_descriptor.as_concrete_TypeRef(),
);
let new_font = CTFont::wrap_under_create_rule(new_font);
*font = Font::from_native_font(new_font);
}
}
}
#[link(name = "CoreText", kind = "framework")]
extern "C" {
fn CTFontCreateCopyWithAttributes(
font: CTFontRef,
size: CGFloat,
matrix: *const CGAffineTransform,
attributes: CTFontDescriptorRef,
) -> CTFontRef;
}
File diff suppressed because it is too large Load Diff
+553
View File
@@ -0,0 +1,553 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
float4 hsla_to_rgba(Hsla hsla);
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *viewport_size);
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii);
float gaussian(float x, float sigma);
float2 erf(float2 x);
float blur_along_x(float x, float y, float sigma, float corner,
float2 half_size);
struct QuadVertexOutput {
float4 position [[position]];
float4 background_color [[flat]];
float4 border_color [[flat]];
uint quad_id [[flat]];
};
vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
uint quad_id [[instance_id]],
constant float2 *unit_vertices
[[buffer(QuadInputIndex_Vertices)]],
constant Quad *quads
[[buffer(QuadInputIndex_Quads)]],
constant Size_DevicePixels *viewport_size
[[buffer(QuadInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Quad quad = quads[quad_id];
float4 device_position = to_device_position(
unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
float4 background_color = hsla_to_rgba(quad.background);
float4 border_color = hsla_to_rgba(quad.border_color);
return QuadVertexOutput{device_position, background_color, border_color,
quad_id};
}
fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
constant Quad *quads
[[buffer(QuadInputIndex_Quads)]]) {
Quad quad = quads[input.quad_id];
float2 half_size =
float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
float2 center =
float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
float2 center_to_point = input.position.xy - center;
float corner_radius;
if (center_to_point.x < 0.) {
if (center_to_point.y < 0.) {
corner_radius = quad.corner_radii.top_left;
} else {
corner_radius = quad.corner_radii.bottom_left;
}
} else {
if (center_to_point.y < 0.) {
corner_radius = quad.corner_radii.top_right;
} else {
corner_radius = quad.corner_radii.bottom_right;
}
}
float2 rounded_edge_to_point =
fabs(center_to_point) - half_size + corner_radius;
float distance =
length(max(0., rounded_edge_to_point)) +
min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
corner_radius;
float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
: quad.border_widths.right;
float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
: quad.border_widths.bottom;
float2 inset_size =
half_size - corner_radius - float2(vertical_border, horizontal_border);
float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
float border_width;
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
border_width = 0.;
} else if (point_to_inset_corner.y > point_to_inset_corner.x) {
border_width = horizontal_border;
} else {
border_width = vertical_border;
}
float4 color;
if (border_width == 0.) {
color = input.background_color;
} else {
float inset_distance = distance + border_width;
// Decrease border's opacity as we move inside the background.
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
// Alpha-blend the border and the background.
float output_alpha =
quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
float3 premultiplied_border_rgb =
input.border_color.rgb * quad.border_color.a;
float3 premultiplied_background_rgb =
input.background_color.rgb * input.background_color.a;
float3 premultiplied_output_rgb =
premultiplied_border_rgb +
premultiplied_background_rgb * (1. - input.border_color.a);
color = float4(premultiplied_output_rgb, output_alpha);
}
return color * float4(1., 1., 1., saturate(0.5 - distance));
}
struct ShadowVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint shadow_id [[flat]];
};
vertex ShadowVertexOutput shadow_vertex(
uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
constant Size_DevicePixels *viewport_size
[[buffer(ShadowInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Shadow shadow = shadows[shadow_id];
float margin = 3. * shadow.blur_radius;
// Set the bounds of the shadow and adjust its size based on the shadow's
// spread radius to achieve the spreading effect
Bounds_ScaledPixels bounds = shadow.bounds;
bounds.origin.x -= margin;
bounds.origin.y -= margin;
bounds.size.width += 2. * margin;
bounds.size.height += 2. * margin;
float4 device_position = to_device_position(
unit_vertex, bounds, shadow.content_mask.bounds, viewport_size);
float4 color = hsla_to_rgba(shadow.color);
return ShadowVertexOutput{
device_position,
color,
shadow_id,
};
}
fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
constant Shadow *shadows
[[buffer(ShadowInputIndex_Shadows)]]) {
Shadow shadow = shadows[input.shadow_id];
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
float2 half_size = size / 2.;
float2 center = origin + half_size;
float2 point = input.position.xy - center;
float corner_radius;
if (point.x < 0.) {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_left;
} else {
corner_radius = shadow.corner_radii.bottom_left;
}
} else {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_right;
} else {
corner_radius = shadow.corner_radii.bottom_right;
}
}
// The signal is only non-zero in a limited range, so don't waste samples
float low = point.y - half_size.y;
float high = point.y + half_size.y;
float start = clamp(-3. * shadow.blur_radius, low, high);
float end = clamp(3. * shadow.blur_radius, low, high);
// Accumulate samples (we can get away with surprisingly few samples)
float step = (end - start) / 4.;
float y = start + step * 0.5;
float alpha = 0.;
for (int i = 0; i < 4; i++) {
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
corner_radius, half_size) *
gaussian(y, shadow.blur_radius) * step;
y += step;
}
return input.color * float4(1., 1., 1., alpha);
}
struct UnderlineVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint underline_id [[flat]];
};
vertex UnderlineVertexOutput underline_vertex(
uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
constant Size_DevicePixels *viewport_size
[[buffer(ShadowInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Underline underline = underlines[underline_id];
float4 device_position =
to_device_position(unit_vertex, underline.bounds,
underline.content_mask.bounds, viewport_size);
float4 color = hsla_to_rgba(underline.color);
return UnderlineVertexOutput{device_position, color, underline_id};
}
fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
constant Underline *underlines
[[buffer(UnderlineInputIndex_Underlines)]]) {
Underline underline = underlines[input.underline_id];
if (underline.wavy) {
float half_thickness = underline.thickness * 0.5;
float2 origin =
float2(underline.bounds.origin.x, underline.bounds.origin.y);
float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
float2(0., 0.5);
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
float amplitude = 1. / (2. * underline.thickness);
float sine = sin(st.x * frequency) * amplitude;
float dSine = cos(st.x * frequency) * amplitude * frequency;
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
float distance_in_pixels = distance * underline.bounds.size.height;
float distance_from_top_border = distance_in_pixels - half_thickness;
float distance_from_bottom_border = distance_in_pixels + half_thickness;
float alpha = saturate(
0.5 - max(-distance_from_bottom_border, distance_from_top_border));
return input.color * float4(1., 1., 1., alpha);
} else {
return input.color;
}
}
struct MonochromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
uint sprite_id [[flat]];
};
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
constant Size_DevicePixels *viewport_size
[[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time
// to make sampling from the texture match the mask.
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color);
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
sprite_id};
}
fragment float4 monochrome_sprite_fragment(
MonochromeSpriteVertexOutput input [[stage_in]],
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
MonochromeSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
Corners_ScaledPixels{0., 0., 0., 0.});
float4 color = input.color;
color.a *= sample.a * saturate(0.5 - clip_distance);
return color;
}
struct PolychromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
uint sprite_id [[flat]];
};
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
constant Size_DevicePixels *viewport_size
[[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
PolychromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time
// to make sampling from the texture match the mask.
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
return PolychromeSpriteVertexOutput{device_position, tile_position,
sprite_id};
}
fragment float4 polychrome_sprite_fragment(
PolychromeSpriteVertexOutput input [[stage_in]],
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PolychromeSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float quad_distance =
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
Corners_ScaledPixels{0., 0., 0., 0.});
float distance = max(quad_distance, clip_distance);
float4 color = sample;
if (sprite.grayscale) {
float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
color.r = grayscale;
color.g = grayscale;
color.b = grayscale;
}
color.a *= saturate(0.5 - distance);
return color;
}
struct PathRasterizationVertexOutput {
float4 position [[position]];
float2 st_position;
float clip_rect_distance [[clip_distance]][4];
};
struct PathRasterizationFragmentInput {
float4 position [[position]];
float2 st_position;
};
vertex PathRasterizationVertexOutput path_rasterization_vertex(
uint vertex_id [[vertex_id]],
constant PathVertex_ScaledPixels *vertices
[[buffer(PathRasterizationInputIndex_Vertices)]],
constant Size_DevicePixels *atlas_size
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
PathVertex_ScaledPixels v = vertices[vertex_id];
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
return PathRasterizationVertexOutput{
float4(vertex_position / viewport_size * float2(2., -2.) +
float2(-1., 1.),
0., 1.),
float2(v.st_position.x, v.st_position.y),
{v.xy_position.x - v.content_mask.bounds.origin.x,
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
v.xy_position.x,
v.xy_position.y - v.content_mask.bounds.origin.y,
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
v.xy_position.y}};
}
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
[[stage_in]]) {
float2 dx = dfdx(input.st_position);
float2 dy = dfdy(input.st_position);
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
(2. * input.st_position.x) * dy.x - dy.y);
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
float distance = f / length(gradient);
float alpha = saturate(0.5 - distance);
return float4(alpha, 0., 0., 1.);
}
struct PathSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
uint sprite_id [[flat]];
};
vertex PathSpriteVertexOutput path_sprite_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
constant Size_DevicePixels *viewport_size
[[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
PathSprite sprite = sprites[sprite_id];
// Don't apply content mask because it was already accounted for when
// rasterizing the path.
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color);
return PathSpriteVertexOutput{device_position, tile_position, color,
sprite_id};
}
fragment float4 path_sprite_fragment(
PathSpriteVertexOutput input [[stage_in]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PathSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float mask = 1. - abs(1. - fmod(sample.r, 2.));
float4 color = input.color;
color.a *= mask;
return color;
}
float4 hsla_to_rgba(Hsla hsla) {
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
float s = hsla.s;
float l = hsla.l;
float a = hsla.a;
float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
float m = l - c / 2.0;
float r = 0.0;
float g = 0.0;
float b = 0.0;
if (h >= 0.0 && h < 1.0) {
r = c;
g = x;
b = 0.0;
} else if (h >= 1.0 && h < 2.0) {
r = x;
g = c;
b = 0.0;
} else if (h >= 2.0 && h < 3.0) {
r = 0.0;
g = c;
b = x;
} else if (h >= 3.0 && h < 4.0) {
r = 0.0;
g = x;
b = c;
} else if (h >= 4.0 && h < 5.0) {
r = x;
g = 0.0;
b = c;
} else {
r = c;
g = 0.0;
b = x;
}
float4 rgba;
rgba.x = (r + m);
rgba.y = (g + m);
rgba.z = (b + m);
rgba.w = a;
return rgba;
}
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *input_viewport_size) {
float2 position =
unit_vertex * float2(bounds.size.width, bounds.size.height) +
float2(bounds.origin.x, bounds.origin.y);
position.x = max(clip_bounds.origin.x, position.x);
position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
position.y = max(clip_bounds.origin.y, position.y);
position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
float2 viewport_size = float2((float)input_viewport_size->width,
(float)input_viewport_size->height);
float2 device_position =
position / viewport_size * float2(2., -2.) + float2(-1., 1.);
return float4(device_position, 0., 1.);
}
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size) {
float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
return (tile_origin + unit_vertex * tile_size) /
float2((float)atlas_size->width, (float)atlas_size->height);
}
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii) {
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
float2 center_to_point = point - center;
float corner_radius;
if (center_to_point.x < 0.) {
if (center_to_point.y < 0.) {
corner_radius = corner_radii.top_left;
} else {
corner_radius = corner_radii.bottom_left;
}
} else {
if (center_to_point.y < 0.) {
corner_radius = corner_radii.top_right;
} else {
corner_radius = corner_radii.bottom_right;
}
}
float2 rounded_edge_to_point =
abs(center_to_point) - half_size + corner_radius;
float distance =
length(max(0., rounded_edge_to_point)) +
min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
corner_radius;
return distance;
}
// A standard gaussian function, used for weighting samples
float gaussian(float x, float sigma) {
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
}
// This approximates the error function, needed for the gaussian integral
float2 erf(float2 x) {
float2 s = sign(x);
float2 a = abs(x);
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
x *= x;
return s - s / (x * x);
}
float blur_along_x(float x, float y, float sigma, float corner,
float2 half_size) {
float delta = min(half_size.y - corner - abs(y), 0.);
float curved =
half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
float2 integral =
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
return integral.y - integral.x;
}
@@ -0,0 +1,752 @@
use crate::{
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
};
use anyhow::anyhow;
use cocoa::appkit::{CGFloat, CGPoint};
use collections::HashMap;
use core_foundation::{
array::CFIndex,
attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
base::{CFRange, TCFType},
string::CFString,
};
use core_graphics::{
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
color_space::CGColorSpace,
context::CGContext,
};
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{
font::Font as FontKitFont,
handle::Handle,
hinting::HintingOptions,
metrics::Metrics,
properties::{Style as FontkitStyle, Weight as FontkitWeight},
source::SystemSource,
sources::mem::MemSource,
};
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use pathfinder_geometry::{
rect::{RectF, RectI},
transform2d::Transform2F,
vector::{Vector2F, Vector2I},
};
use smallvec::SmallVec;
use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
use super::open_type;
#[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7;
pub struct MacTextSystem(RwLock<MacTextSystemState>);
struct MacTextSystemState {
memory_source: MemSource,
system_source: SystemSource,
fonts: Vec<FontKitFont>,
font_selections: HashMap<Font, FontId>,
font_ids_by_postscript_name: HashMap<String, FontId>,
font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
postscript_names_by_font_id: HashMap<FontId, String>,
}
impl MacTextSystem {
pub fn new() -> Self {
Self(RwLock::new(MacTextSystemState {
memory_source: MemSource::empty(),
system_source: SystemSource::new(),
fonts: Vec::new(),
font_selections: HashMap::default(),
font_ids_by_postscript_name: HashMap::default(),
font_ids_by_family_name: HashMap::default(),
postscript_names_by_font_id: HashMap::default(),
}))
}
}
impl Default for MacTextSystem {
fn default() -> Self {
Self::new()
}
}
impl PlatformTextSystem for MacTextSystem {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
self.0.write().add_fonts(fonts)
}
fn all_font_families(&self) -> Vec<String> {
self.0
.read()
.system_source
.all_families()
.expect("core text should never return an error")
}
fn font_id(&self, font: &Font) -> Result<FontId> {
let lock = self.0.upgradable_read();
if let Some(font_id) = lock.font_selections.get(font) {
Ok(*font_id)
} else {
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
{
font_ids.as_slice()
} else {
let font_ids = lock.load_family(&font.family, font.features)?;
lock.font_ids_by_family_name
.insert(font.family.clone(), font_ids);
lock.font_ids_by_family_name[&font.family].as_ref()
};
let candidate_properties = candidates
.iter()
.map(|font_id| lock.fonts[font_id.0].properties())
.collect::<SmallVec<[_; 4]>>();
let ix = font_kit::matching::find_best_match(
&candidate_properties,
&font_kit::properties::Properties {
style: font.style.into(),
weight: font.weight.into(),
stretch: Default::default(),
},
)?;
Ok(candidates[ix])
}
}
fn font_metrics(&self, font_id: FontId) -> FontMetrics {
self.0.read().fonts[font_id.0].metrics().into()
}
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
Ok(self.0.read().fonts[font_id.0]
.typographic_bounds(glyph_id.into())?
.into())
}
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
self.0.read().advance(font_id, glyph_id)
}
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
self.0.read().glyph_for_char(font_id, ch)
}
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
self.0.read().raster_bounds(params)
}
fn rasterize_glyph(
&self,
glyph_id: &RenderGlyphParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
self.0.read().rasterize_glyph(glyph_id)
}
fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
self.0.write().layout_line(text, font_size, font_runs)
}
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize> {
self.0.read().wrap_line(text, font_id, font_size, width)
}
}
impl MacTextSystemState {
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
self.memory_source.add_fonts(
fonts
.iter()
.map(|bytes| Handle::from_memory(bytes.clone(), 0)),
)?;
Ok(())
}
fn load_family(
&mut self,
name: &SharedString,
features: FontFeatures,
) -> Result<SmallVec<[FontId; 4]>> {
let mut font_ids = SmallVec::new();
let family = self
.memory_source
.select_family_by_name(name.as_ref())
.or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
for font in family.fonts() {
let mut font = font.load()?;
open_type::apply_features(&mut font, features);
let font_id = FontId(self.fonts.len());
font_ids.push(font_id);
let postscript_name = font.postscript_name().unwrap();
self.font_ids_by_postscript_name
.insert(postscript_name.clone(), font_id);
self.postscript_names_by_font_id
.insert(font_id, postscript_name);
self.fonts.push(font);
}
Ok(font_ids)
}
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
}
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
}
fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
let postscript_name = requested_font.postscript_name();
if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
*font_id
} else {
let font_id = FontId(self.fonts.len());
self.font_ids_by_postscript_name
.insert(postscript_name.clone(), font_id);
self.postscript_names_by_font_id
.insert(font_id, postscript_name);
self.fonts
.push(font_kit::font::Font::from_core_graphics_font(
requested_font.copy_to_CGFont(),
));
font_id
}
}
fn is_emoji(&self, font_id: FontId) -> bool {
self.postscript_names_by_font_id
.get(&font_id)
.map_or(false, |postscript_name| {
postscript_name == "AppleColorEmoji"
})
}
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let font = &self.fonts[params.font_id.0];
let scale = Transform2F::from_scale(params.scale_factor);
Ok(font
.raster_bounds(
params.glyph_id.into(),
params.font_size.into(),
scale,
HintingOptions::None,
font_kit::canvas::RasterizationOptions::GrayscaleAa,
)?
.into())
}
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)> {
let glyph_bounds = self.raster_bounds(params)?;
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
Err(anyhow!("glyph bounds are empty"))
} else {
// Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
let mut bitmap_size = glyph_bounds.size;
if params.subpixel_variant.x > 0 {
bitmap_size.width += DevicePixels(1);
}
if params.subpixel_variant.y > 0 {
bitmap_size.height += DevicePixels(1);
}
let mut bytes;
let cx;
if params.is_emoji {
bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
bitmap_size.width.0 as usize,
bitmap_size.height.0 as usize,
8,
bitmap_size.width.0 as usize * 4,
&CGColorSpace::create_device_rgb(),
kCGImageAlphaPremultipliedLast,
);
} else {
bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
bitmap_size.width.0 as usize,
bitmap_size.height.0 as usize,
8,
bitmap_size.width.0 as usize,
&CGColorSpace::create_device_gray(),
kCGImageAlphaOnly,
);
}
// Move the origin to bottom left and account for scaling, this
// makes drawing text consistent with the font-kit's raster_bounds.
cx.translate(
-glyph_bounds.origin.x.0 as CGFloat,
(glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
);
cx.scale(
params.scale_factor as CGFloat,
params.scale_factor as CGFloat,
);
let subpixel_shift = params
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
cx.set_allows_font_subpixel_positioning(true);
cx.set_should_subpixel_position_fonts(true);
cx.set_allows_font_subpixel_quantization(false);
cx.set_should_subpixel_quantize_fonts(false);
self.fonts[params.font_id.0]
.native_font()
.clone_with_font_size(f32::from(params.font_size) as CGFloat)
.draw_glyphs(
&[u32::from(params.glyph_id) as CGGlyph],
&[CGPoint::new(
(subpixel_shift.x / params.scale_factor) as CGFloat,
(subpixel_shift.y / params.scale_factor) as CGFloat,
)],
cx,
);
if params.is_emoji {
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
for pixel in bytes.chunks_exact_mut(4) {
pixel.swap(0, 2);
let a = pixel[3] as f32 / 255.;
pixel[0] = (pixel[0] as f32 / a) as u8;
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
}
Ok((bitmap_size.into(), bytes))
}
}
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
let mut string = CFMutableAttributedString::new();
{
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let utf16_line_len = string.char_len() as usize;
let mut ix_converter = StringIndexConverter::new(text);
for run in font_runs {
let utf8_end = ix_converter.utf8_ix + run.len;
let utf16_start = ix_converter.utf16_ix;
if utf16_start >= utf16_line_len {
break;
}
ix_converter.advance_to_utf8_ix(utf8_end);
let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
let cf_range =
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
let font: &FontKitFont = &self.fonts[run.font_id.0];
unsafe {
string.set_attribute(
cf_range,
kCTFontAttributeName,
&font.native_font().clone_with_font_size(font_size.into()),
);
}
if utf16_end == utf16_line_len {
break;
}
}
}
// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
let mut runs = Vec::new();
for run in line.glyph_runs().into_iter() {
let attributes = run.attributes().unwrap();
let font = unsafe {
attributes
.get(kCTFontAttributeName)
.downcast::<CTFont>()
.unwrap()
};
let font_id = self.id_for_native_font(font);
let mut ix_converter = StringIndexConverter::new(text);
let mut glyphs = SmallVec::new();
for ((glyph_id, position), glyph_utf16_ix) in run
.glyphs()
.iter()
.zip(run.positions().iter())
.zip(run.string_indices().iter())
{
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
glyphs.push(ShapedGlyph {
id: (*glyph_id).into(),
position: point(position.x as f32, position.y as f32).map(px),
index: ix_converter.utf8_ix,
is_emoji: self.is_emoji(font_id),
});
}
runs.push(ShapedRun { font_id, glyphs })
}
let typographic_bounds = line.get_typographic_bounds();
LineLayout {
width: typographic_bounds.width.into(),
ascent: typographic_bounds.ascent.into(),
descent: typographic_bounds.descent.into(),
runs,
font_size,
}
}
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize> {
let mut string = CFMutableAttributedString::new();
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
let font = &self.fonts[font_id.0];
unsafe {
string.set_attribute(
cf_range,
kCTFontAttributeName,
&font.native_font().clone_with_font_size(font_size.into()),
);
let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
let mut ix_converter = StringIndexConverter::new(text);
let mut break_indices = Vec::new();
while ix_converter.utf8_ix < text.len() {
let utf16_len = CTTypesetterSuggestLineBreak(
typesetter,
ix_converter.utf16_ix as isize,
width.into(),
) as usize;
ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
if ix_converter.utf8_ix >= text.len() {
break;
}
break_indices.push(ix_converter.utf8_ix as usize);
}
break_indices
}
}
}
#[derive(Clone)]
struct StringIndexConverter<'a> {
text: &'a str,
utf8_ix: usize,
utf16_ix: usize,
}
impl<'a> StringIndexConverter<'a> {
fn new(text: &'a str) -> Self {
Self {
text,
utf8_ix: 0,
utf16_ix: 0,
}
}
fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
if self.utf8_ix + ix >= utf8_target {
self.utf8_ix += ix;
return;
}
self.utf16_ix += c.len_utf16();
}
self.utf8_ix = self.text.len();
}
fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
if self.utf16_ix >= utf16_target {
self.utf8_ix += ix;
return;
}
self.utf16_ix += c.len_utf16();
}
self.utf8_ix = self.text.len();
}
}
#[repr(C)]
pub struct __CFTypesetter(c_void);
pub type CTTypesetterRef = *const __CFTypesetter;
#[link(name = "CoreText", kind = "framework")]
extern "C" {
fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
fn CTTypesetterSuggestLineBreak(
typesetter: CTTypesetterRef,
start_index: CFIndex,
width: f64,
) -> CFIndex;
}
impl From<Metrics> for FontMetrics {
fn from(metrics: Metrics) -> Self {
FontMetrics {
units_per_em: metrics.units_per_em,
ascent: metrics.ascent,
descent: metrics.descent,
line_gap: metrics.line_gap,
underline_position: metrics.underline_position,
underline_thickness: metrics.underline_thickness,
cap_height: metrics.cap_height,
x_height: metrics.x_height,
bounding_box: metrics.bounding_box.into(),
}
}
}
impl From<RectF> for Bounds<f32> {
fn from(rect: RectF) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<RectI> for Bounds<DevicePixels> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
}
}
}
impl From<Vector2I> for Size<DevicePixels> {
fn from(value: Vector2I) -> Self {
size(value.x().into(), value.y().into())
}
}
impl From<RectI> for Bounds<i32> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<Point<u32>> for Vector2I {
fn from(size: Point<u32>) -> Self {
Vector2I::new(size.x as i32, size.y as i32)
}
}
impl From<Vector2F> for Size<f32> {
fn from(vec: Vector2F) -> Self {
size(vec.x(), vec.y())
}
}
impl From<FontWeight> for FontkitWeight {
fn from(value: FontWeight) -> Self {
FontkitWeight(value.0)
}
}
impl From<FontStyle> for FontkitStyle {
fn from(style: FontStyle) -> Self {
match style {
FontStyle::Normal => FontkitStyle::Normal,
FontStyle::Italic => FontkitStyle::Italic,
FontStyle::Oblique => FontkitStyle::Oblique,
}
}
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::AppContext;
// use font_kit::properties::{Style, Weight};
// use platform::FontSystem as _;
// #[crate::test(self, retries = 5)]
// fn test_layout_str(_: &mut AppContext) {
// // This is failing intermittently on CI and we don't have time to figure it out
// let fonts = FontSystem::new();
// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
// let menlo_regular = RunStyle {
// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// let menlo_italic = RunStyle {
// font_id: fonts
// .select_font(&menlo, Properties::new().style(Style::Italic))
// .unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// let menlo_bold = RunStyle {
// font_id: fonts
// .select_font(&menlo, Properties::new().weight(Weight::BOLD))
// .unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// assert_ne!(menlo_regular, menlo_italic);
// assert_ne!(menlo_regular, menlo_bold);
// assert_ne!(menlo_italic, menlo_bold);
// let line = fonts.layout_line(
// "hello world",
// 16.0,
// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
// );
// assert_eq!(line.runs.len(), 3);
// assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
// assert_eq!(line.runs[0].glyphs.len(), 2);
// assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
// assert_eq!(line.runs[1].glyphs.len(), 4);
// assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
// assert_eq!(line.runs[2].glyphs.len(), 5);
// }
// #[test]
// fn test_glyph_offsets() -> crate::Result<()> {
// let fonts = FontSystem::new();
// let zapfino = fonts.load_family("Zapfino", &Default::default())?;
// let zapfino_regular = RunStyle {
// font_id: fonts.select_font(&zapfino, &Properties::new())?,
// color: Default::default(),
// underline: Default::default(),
// };
// let menlo = fonts.load_family("Menlo", &Default::default())?;
// let menlo_regular = RunStyle {
// font_id: fonts.select_font(&menlo, &Properties::new())?,
// color: Default::default(),
// underline: Default::default(),
// };
// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
// let line = fonts.layout_line(
// text,
// 16.0,
// &[
// (9, zapfino_regular),
// (13, menlo_regular),
// (text.len() - 22, zapfino_regular),
// ],
// );
// assert_eq!(
// line.runs
// .iter()
// .flat_map(|r| r.glyphs.iter())
// .map(|g| g.index)
// .collect::<Vec<_>>(),
// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
// );
// Ok(())
// }
// #[test]
// #[ignore]
// fn test_rasterize_glyph() {
// use std::{fs::File, io::BufWriter, path::Path};
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
// const VARIANTS: usize = 1;
// for i in 0..VARIANTS {
// let variant = i as f32 / VARIANTS as f32;
// let (bounds, bytes) = fonts
// .rasterize_glyph(
// font_id,
// 16.0,
// glyph_id,
// vec2f(variant, variant),
// 2.,
// RasterizationOptions::Alpha,
// )
// .unwrap();
// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
// let path = Path::new(&name);
// let file = File::create(path).unwrap();
// let w = &mut BufWriter::new(file);
// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
// encoder.set_color(png::ColorType::Grayscale);
// encoder.set_depth(png::BitDepth::Eight);
// let mut writer = encoder.write_header().unwrap();
// writer.write_image_data(&bytes).unwrap();
// }
// }
// #[test]
// fn test_wrap_line() {
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
// let line = "one two three four five\n";
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
// assert_eq!(
// wrap_boundaries,
// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
// );
// }
// #[test]
// fn test_layout_line_bom_char() {
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
// let style = RunStyle {
// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// let line = "\u{feff}";
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
// assert_eq!(layout.len, line.len());
// assert!(layout.runs.is_empty());
// let line = "a\u{feff}b";
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
// assert_eq!(layout.len, line.len());
// assert_eq!(layout.runs.len(), 1);
// assert_eq!(layout.runs[0].glyphs.len(), 2);
// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
// // There's no glyph for \u{feff}
// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
// }
// }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
use crate::WindowAppearance;
use cocoa::{
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
base::id,
foundation::NSString,
};
use objc::{msg_send, sel, sel_impl};
use std::ffi::CStr;
impl WindowAppearance {
pub unsafe fn from_native(appearance: id) -> Self {
let name: id = msg_send![appearance, name];
if name == NSAppearanceNameVibrantLight {
Self::VibrantLight
} else if name == NSAppearanceNameVibrantDark {
Self::VibrantDark
} else if name == NSAppearanceNameAqua {
Self::Light
} else if name == NSAppearanceNameDarkAqua {
Self::Dark
} else {
println!(
"unknown appearance: {:?}",
CStr::from_ptr(name.UTF8String())
);
Self::Light
}
}
}
#[link(name = "AppKit", kind = "framework")]
extern "C" {
pub static NSAppearanceNameAqua: id;
pub static NSAppearanceNameDarkAqua: id;
}
+188
View File
@@ -0,0 +1,188 @@
use super::Platform;
use crate::{DisplayId, Executor};
pub struct TestPlatform;
impl TestPlatform {
pub fn new() -> Self {
TestPlatform
}
}
// todo!("implement out what our tests needed in GPUI 1")
impl Platform for TestPlatform {
fn executor(&self) -> Executor {
unimplemented!()
}
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
unimplemented!()
}
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
unimplemented!()
}
fn quit(&self) {
unimplemented!()
}
fn restart(&self) {
unimplemented!()
}
fn activate(&self, _ignoring_other_apps: bool) {
unimplemented!()
}
fn hide(&self) {
unimplemented!()
}
fn hide_other_apps(&self) {
unimplemented!()
}
fn unhide_other_apps(&self) {
unimplemented!()
}
fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
unimplemented!()
}
fn display(&self, _id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
unimplemented!()
}
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
unimplemented!()
}
fn open_window(
&self,
_handle: crate::AnyWindowHandle,
_options: crate::WindowOptions,
) -> Box<dyn crate::PlatformWindow> {
unimplemented!()
}
fn set_display_link_output_callback(
&self,
_display_id: DisplayId,
_callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp)>,
) {
unimplemented!()
}
fn start_display_link(&self, _display_id: DisplayId) {
unimplemented!()
}
fn stop_display_link(&self, _display_id: DisplayId) {
unimplemented!()
}
fn open_url(&self, _url: &str) {
unimplemented!()
}
fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
unimplemented!()
}
fn prompt_for_paths(
&self,
_options: crate::PathPromptOptions,
) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
unimplemented!()
}
fn prompt_for_new_path(
&self,
_directory: &std::path::Path,
) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
unimplemented!()
}
fn reveal_path(&self, _path: &std::path::Path) {
unimplemented!()
}
fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_quit(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_event(&self, _callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
unimplemented!()
}
fn os_name(&self) -> &'static str {
unimplemented!()
}
fn os_version(&self) -> anyhow::Result<crate::SemanticVersion> {
unimplemented!()
}
fn app_version(&self) -> anyhow::Result<crate::SemanticVersion> {
unimplemented!()
}
fn app_path(&self) -> anyhow::Result<std::path::PathBuf> {
unimplemented!()
}
fn local_timezone(&self) -> time::UtcOffset {
unimplemented!()
}
fn path_for_auxiliary_executable(&self, _name: &str) -> anyhow::Result<std::path::PathBuf> {
unimplemented!()
}
fn set_cursor_style(&self, _style: crate::CursorStyle) {
unimplemented!()
}
fn should_auto_hide_scrollbars(&self) -> bool {
unimplemented!()
}
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {
unimplemented!()
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
unimplemented!()
}
fn write_credentials(
&self,
_url: &str,
_username: &str,
_password: &[u8],
) -> anyhow::Result<()> {
unimplemented!()
}
fn read_credentials(&self, _url: &str) -> anyhow::Result<Option<(String, Vec<u8>)>> {
unimplemented!()
}
fn delete_credentials(&self, _url: &str) -> anyhow::Result<()> {
unimplemented!()
}
}
+829
View File
@@ -0,0 +1,829 @@
use crate::{
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
ScaledPixels, StackingOrder,
};
use collections::BTreeMap;
use etagere::euclid::{Point3D, Vector3D};
use plane_split::{BspSplitter, Polygon as BspPolygon};
use std::{fmt::Debug, iter::Peekable, mem, slice};
// Exported to metal
pub(crate) type PointF = Point<f32>;
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub type LayerId = u32;
pub type DrawOrder = u32;
pub(crate) struct SceneBuilder {
layers_by_order: BTreeMap<StackingOrder, LayerId>,
splitter: BspSplitter<(PrimitiveKind, usize)>,
shadows: Vec<Shadow>,
quads: Vec<Quad>,
paths: Vec<Path<ScaledPixels>>,
underlines: Vec<Underline>,
monochrome_sprites: Vec<MonochromeSprite>,
polychrome_sprites: Vec<PolychromeSprite>,
}
impl SceneBuilder {
pub fn new() -> SceneBuilder {
SceneBuilder {
layers_by_order: BTreeMap::new(),
splitter: BspSplitter::new(),
shadows: Vec::new(),
quads: Vec::new(),
paths: Vec::new(),
underlines: Vec::new(),
monochrome_sprites: Vec::new(),
polychrome_sprites: Vec::new(),
}
}
pub fn build(&mut self) -> Scene {
// Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
let mut layer_z_values = vec![0.; self.layers_by_order.len()];
for (ix, layer_id) in self.layers_by_order.values().enumerate() {
layer_z_values[*layer_id as usize] = ix as f32 / self.layers_by_order.len() as f32;
}
self.layers_by_order.clear();
// Add all primitives to the BSP splitter to determine draw order
self.splitter.reset();
for (ix, shadow) in self.shadows.iter().enumerate() {
let z = layer_z_values[shadow.order as LayerId as usize];
self.splitter
.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
}
for (ix, quad) in self.quads.iter().enumerate() {
let z = layer_z_values[quad.order as LayerId as usize];
self.splitter
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
}
for (ix, path) in self.paths.iter().enumerate() {
let z = layer_z_values[path.order as LayerId as usize];
self.splitter
.add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
}
for (ix, underline) in self.underlines.iter().enumerate() {
let z = layer_z_values[underline.order as LayerId as usize];
self.splitter.add(
underline
.bounds
.to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
);
}
for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
let z = layer_z_values[monochrome_sprite.order as LayerId as usize];
self.splitter.add(
monochrome_sprite
.bounds
.to_bsp_polygon(z, (PrimitiveKind::MonochromeSprite, ix)),
);
}
for (ix, polychrome_sprite) in self.polychrome_sprites.iter().enumerate() {
let z = layer_z_values[polychrome_sprite.order as LayerId as usize];
self.splitter.add(
polychrome_sprite
.bounds
.to_bsp_polygon(z, (PrimitiveKind::PolychromeSprite, ix)),
);
}
// Sort all polygons, then reassign the order field of each primitive to `draw_order`
// We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
for (draw_order, polygon) in self
.splitter
.sort(Vector3D::new(0., 0., 1.))
.iter()
.enumerate()
{
match polygon.anchor {
(PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Path, ix) => self.paths[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Underline, ix) => {
self.underlines[ix].order = draw_order as DrawOrder
}
(PrimitiveKind::MonochromeSprite, ix) => {
self.monochrome_sprites[ix].order = draw_order as DrawOrder
}
(PrimitiveKind::PolychromeSprite, ix) => {
self.polychrome_sprites[ix].order = draw_order as DrawOrder
}
}
}
self.shadows.sort_unstable();
self.quads.sort_unstable();
self.paths.sort_unstable();
self.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable();
Scene {
shadows: mem::take(&mut self.shadows),
quads: mem::take(&mut self.quads),
paths: mem::take(&mut self.paths),
underlines: mem::take(&mut self.underlines),
monochrome_sprites: mem::take(&mut self.monochrome_sprites),
polychrome_sprites: mem::take(&mut self.polychrome_sprites),
}
}
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
let primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.size.width <= ScaledPixels(0.)
|| clipped_bounds.size.height <= ScaledPixels(0.)
{
return;
}
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
*layer_id
} else {
let next_id = self.layers_by_order.len() as LayerId;
self.layers_by_order.insert(order.clone(), next_id);
next_id
};
match primitive {
Primitive::Shadow(mut shadow) => {
shadow.order = layer_id;
self.shadows.push(shadow);
}
Primitive::Quad(mut quad) => {
quad.order = layer_id;
self.quads.push(quad);
}
Primitive::Path(mut path) => {
path.order = layer_id;
path.id = PathId(self.paths.len());
self.paths.push(path);
}
Primitive::Underline(mut underline) => {
underline.order = layer_id;
self.underlines.push(underline);
}
Primitive::MonochromeSprite(mut sprite) => {
sprite.order = layer_id;
self.monochrome_sprites.push(sprite);
}
Primitive::PolychromeSprite(mut sprite) => {
sprite.order = layer_id;
self.polychrome_sprites.push(sprite);
}
}
}
}
pub(crate) struct Scene {
pub shadows: Vec<Shadow>,
pub quads: Vec<Quad>,
pub paths: Vec<Path<ScaledPixels>>,
pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
}
impl Scene {
#[allow(dead_code)]
pub fn paths(&self) -> &[Path<ScaledPixels>] {
&self.paths
}
pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
BatchIterator {
shadows: &self.shadows,
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
quads: &self.quads,
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
paths: &self.paths,
paths_start: 0,
paths_iter: self.paths.iter().peekable(),
underlines: &self.underlines,
underlines_start: 0,
underlines_iter: self.underlines.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
polychrome_sprites: &self.polychrome_sprites,
polychrome_sprites_start: 0,
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
}
}
}
struct BatchIterator<'a> {
shadows: &'a [Shadow],
shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
quads: &'a [Quad],
quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>,
paths: &'a [Path<ScaledPixels>],
paths_start: usize,
paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
underlines: &'a [Underline],
underlines_start: usize,
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
polychrome_sprites: &'a [PolychromeSprite],
polychrome_sprites_start: usize,
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
}
impl<'a> Iterator for BatchIterator<'a> {
type Item = PrimitiveBatch<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut orders_and_kinds = [
(
self.shadows_iter.peek().map(|s| s.order),
PrimitiveKind::Shadow,
),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
(self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
(
self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline,
),
(
self.monochrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::MonochromeSprite,
),
(
self.polychrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::PolychromeSprite,
),
];
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
let first = orders_and_kinds[0];
let second = orders_and_kinds[1];
let (batch_kind, max_order) = if first.0.is_some() {
(first.1, second.0.unwrap_or(u32::MAX))
} else {
return None;
};
match batch_kind {
PrimitiveKind::Shadow => {
let shadows_start = self.shadows_start;
let mut shadows_end = shadows_start;
while self
.shadows_iter
.next_if(|shadow| shadow.order <= max_order)
.is_some()
{
shadows_end += 1;
}
self.shadows_start = shadows_end;
Some(PrimitiveBatch::Shadows(
&self.shadows[shadows_start..shadows_end],
))
}
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start;
while self
.quads_iter
.next_if(|quad| quad.order <= max_order)
.is_some()
{
quads_end += 1;
}
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
PrimitiveKind::Path => {
let paths_start = self.paths_start;
let mut paths_end = paths_start;
while self
.paths_iter
.next_if(|path| path.order <= max_order)
.is_some()
{
paths_end += 1;
}
self.paths_start = paths_end;
Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
}
PrimitiveKind::Underline => {
let underlines_start = self.underlines_start;
let mut underlines_end = underlines_start;
while self
.underlines_iter
.next_if(|underline| underline.order <= max_order)
.is_some()
{
underlines_end += 1;
}
self.underlines_start = underlines_end;
Some(PrimitiveBatch::Underlines(
&self.underlines[underlines_start..underlines_end],
))
}
PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
let mut sprites_end = sprites_start;
while self
.monochrome_sprites_iter
.next_if(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.monochrome_sprites_start = sprites_end;
Some(PrimitiveBatch::MonochromeSprites {
texture_id,
sprites: &self.monochrome_sprites[sprites_start..sprites_end],
})
}
PrimitiveKind::PolychromeSprite => {
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.polychrome_sprites_start;
let mut sprites_end = self.polychrome_sprites_start;
while self
.polychrome_sprites_iter
.next_if(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.polychrome_sprites_start = sprites_end;
Some(PrimitiveBatch::PolychromeSprites {
texture_id,
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
})
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
pub enum PrimitiveKind {
Shadow,
#[default]
Quad,
Path,
Underline,
MonochromeSprite,
PolychromeSprite,
}
pub enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
}
impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.bounds,
Primitive::Quad(quad) => &quad.bounds,
Primitive::Path(path) => &path.bounds,
Primitive::Underline(underline) => &underline.bounds,
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
}
}
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.content_mask,
Primitive::Quad(quad) => &quad.content_mask,
Primitive::Path(path) => &path.content_mask,
Primitive::Underline(underline) => &underline.content_mask,
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
}
}
}
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
Quads(&'a [Quad]),
Paths(&'a [Path<ScaledPixels>]),
Underlines(&'a [Underline]),
MonochromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite],
},
PolychromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [PolychromeSprite],
},
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Quad {
pub order: u32, // Initially a LayerId, then a DrawOrder.
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub background: Hsla,
pub border_color: Hsla,
pub corner_radii: Corners<ScaledPixels>,
pub border_widths: Edges<ScaledPixels>,
}
impl Ord for Quad {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Quad {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Quad> for Primitive {
fn from(quad: Quad) -> Self {
Primitive::Quad(quad)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Underline {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub thickness: ScaledPixels,
pub color: Hsla,
pub wavy: bool,
}
impl Ord for Underline {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Underline {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Underline> for Primitive {
fn from(underline: Underline) -> Self {
Primitive::Underline(underline)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Shadow {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub blur_radius: ScaledPixels,
}
impl Ord for Shadow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Shadow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Shadow> for Primitive {
fn from(shadow: Shadow) -> Self {
Primitive::Shadow(shadow)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct MonochromeSprite {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
}
impl Ord for MonochromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for MonochromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<MonochromeSprite> for Primitive {
fn from(sprite: MonochromeSprite) -> Self {
Primitive::MonochromeSprite(sprite)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct PolychromeSprite {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
pub grayscale: bool,
}
impl Ord for PolychromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for PolychromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<PolychromeSprite> for Primitive {
fn from(sprite: PolychromeSprite) -> Self {
Primitive::PolychromeSprite(sprite)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize);
#[derive(Debug)]
pub struct Path<P: Clone + Default + Debug> {
pub(crate) id: PathId,
order: u32,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
pub(crate) color: Hsla,
start: Point<P>,
current: Point<P>,
contour_count: usize,
}
impl Path<Pixels> {
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
order: 0,
vertices: Vec::new(),
start,
current: start,
bounds: Bounds {
origin: start,
size: Default::default(),
},
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
}
}
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
.map(|vertex| vertex.scale(factor))
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
}
}
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.current = to;
}
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.push_triangle(
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
self.current = to;
}
fn push_triangle(
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
self.bounds = self
.bounds
.union(&Bounds {
origin: xy.0,
size: Default::default(),
})
.union(&Bounds {
origin: xy.1,
size: Default::default(),
})
.union(&Bounds {
origin: xy.2,
size: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.0,
st_position: st.0,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.1,
st_position: st.1,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.2,
st_position: st.2,
content_mask: Default::default(),
});
}
}
impl Eq for Path<ScaledPixels> {}
impl PartialEq for Path<ScaledPixels> {
fn eq(&self, other: &Self) -> bool {
self.order == other.order
}
}
impl Ord for Path<ScaledPixels> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Path<ScaledPixels> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Path<ScaledPixels>> for Primitive {
fn from(path: Path<ScaledPixels>) -> Self {
Primitive::Path(path)
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct PathVertex<P: Clone + Default + Debug> {
pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
}
impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {
xy_position: self.xy_position.scale(factor),
st_position: self.st_position,
content_mask: self.content_mask.scale(factor),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct AtlasId(pub(crate) usize);
impl Bounds<ScaledPixels> {
fn to_bsp_polygon<A: Copy>(&self, z: f32, anchor: A) -> BspPolygon<A> {
let upper_left = self.origin;
let upper_right = self.upper_right();
let lower_right = self.lower_right();
let lower_left = self.lower_left();
BspPolygon::from_points(
[
Point3D::new(upper_left.x.into(), upper_left.y.into(), z as f64),
Point3D::new(upper_right.x.into(), upper_right.y.into(), z as f64),
Point3D::new(lower_right.x.into(), lower_right.y.into(), z as f64),
Point3D::new(lower_left.x.into(), lower_left.y.into(), z as f64),
],
anchor,
)
.expect("Polygon should not be empty")
}
}
#[cfg(test)]
mod tests {
use crate::{point, size};
use super::*;
use smallvec::smallvec;
#[test]
fn test_scene() {
let mut scene = SceneBuilder::new();
assert_eq!(scene.layers_by_order.len(), 0);
scene.insert(&smallvec![1].into(), quad());
scene.insert(&smallvec![2].into(), shadow());
scene.insert(&smallvec![3].into(), quad());
let mut batches_count = 0;
for _ in scene.build().batches() {
batches_count += 1;
}
assert_eq!(batches_count, 3);
}
fn quad() -> Quad {
Quad {
order: 0,
bounds: Bounds {
origin: point(ScaledPixels(0.), ScaledPixels(0.)),
size: size(ScaledPixels(100.), ScaledPixels(100.)),
},
content_mask: Default::default(),
background: Default::default(),
border_color: Default::default(),
corner_radii: Default::default(),
border_widths: Default::default(),
}
}
fn shadow() -> Shadow {
Shadow {
order: Default::default(),
bounds: Bounds {
origin: point(ScaledPixels(0.), ScaledPixels(0.)),
size: size(ScaledPixels(100.), ScaledPixels(100.)),
},
corner_radii: Default::default(),
content_mask: Default::default(),
color: Default::default(),
blur_radius: Default::default(),
}
}
}
+419
View File
@@ -0,0 +1,419 @@
use crate::{
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result,
SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
};
use refineable::{Cascade, Refineable};
use smallvec::SmallVec;
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
};
pub type StyleCascade = Cascade<Style>;
#[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 background: Option<Fill>,
/// The border color of this element
pub border_color: Option<Hsla>,
/// The radius of the corners of this element
#[refineable]
pub corner_radii: Corners<AbsoluteLength>,
/// Box Shadow of the element
pub box_shadow: SmallVec<[BoxShadow; 2]>,
/// TEXT
pub text: TextStyleRefinement,
pub z_index: Option<u32>,
}
impl Styled for StyleRefinement {
fn style(&mut self) -> &mut StyleRefinement {
self
}
}
#[derive(Clone, Debug)]
pub struct BoxShadow {
pub color: Hsla,
pub offset: Point<Pixels>,
pub blur_radius: Pixels,
pub spread_radius: Pixels,
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct TextStyle {
pub color: Hsla,
pub font_family: SharedString,
pub font_features: FontFeatures,
pub font_size: Rems,
pub line_height: DefiniteLength,
pub font_weight: FontWeight,
pub font_style: FontStyle,
pub underline: Option<UnderlineStyle>,
}
impl Default for TextStyle {
fn default() -> Self {
TextStyle {
color: black(),
font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system")
font_features: FontFeatures::default(),
font_size: rems(1.),
line_height: phi(),
font_weight: FontWeight::default(),
font_style: FontStyle::default(),
underline: None,
}
}
}
impl TextStyle {
pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
if let Some(weight) = style.font_weight {
self.font_weight = weight;
}
if let Some(style) = style.font_style {
self.font_style = style;
}
if let Some(color) = style.color {
self.color = self.color.blend(color);
}
if let Some(factor) = style.fade_out {
self.color.fade_out(factor);
}
if let Some(underline) = style.underline {
self.underline = Some(underline);
}
Ok(self)
}
pub fn to_run(&self, len: usize) -> TextRun {
TextRun {
len,
font: Font {
family: self.font_family.clone(),
features: Default::default(),
weight: self.font_weight,
style: self.font_style,
},
color: self.color,
underline: self.underline.clone(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HighlightStyle {
pub color: Option<Hsla>,
pub font_weight: Option<FontWeight>,
pub font_style: Option<FontStyle>,
pub underline: Option<UnderlineStyle>,
pub fade_out: Option<f32>,
}
impl Eq for HighlightStyle {}
impl Style {
pub fn text_style(&self, _cx: &WindowContext) -> Option<&TextStyleRefinement> {
if self.text.is_some() {
Some(&self.text)
} else {
None
}
}
pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
where
C: BorrowAppContext,
F: FnOnce(&mut C) -> R,
{
if self.text.is_some() {
cx.with_text_style(self.text.clone(), f)
} else {
f(cx)
}
}
/// Apply overflow to content mask
pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
where
C: BorrowWindow,
F: FnOnce(&mut C) -> R,
{
let current_mask = cx.content_mask();
let min = current_mask.bounds.origin;
let max = current_mask.bounds.lower_right();
let mask_bounds = match (
self.overflow.x == Overflow::Visible,
self.overflow.y == Overflow::Visible,
) {
// x and y both visible
(true, true) => return f(cx),
// x visible, y hidden
(true, false) => Bounds::from_corners(
point(min.x, bounds.origin.y),
point(max.x, bounds.lower_right().y),
),
// x hidden, y visible
(false, true) => Bounds::from_corners(
point(bounds.origin.x, min.y),
point(bounds.lower_right().x, max.y),
),
// both hidden
(false, false) => bounds,
};
let mask = ContentMask {
bounds: mask_bounds,
};
cx.with_content_mask(mask, f)
}
/// Paints the background of an element styled with this style.
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
cx.stack(0, |cx| {
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
});
let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
cx.stack(1, |cx| {
cx.paint_quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
self.border_widths.to_pixels(rem_size),
self.border_color.unwrap_or_default(),
);
});
}
}
fn is_border_visible(&self) -> bool {
self.border_color
.map_or(false, |color| !color.is_transparent())
&& self.border_widths.any(|length| !length.is_zero())
}
}
impl Default for Style {
fn default() -> Self {
Style {
display: Display::Block,
overflow: Point {
x: Overflow::Visible,
y: Overflow::Visible,
},
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),
padding: Edges::<DefiniteLength>::zero(),
border_widths: Edges::<AbsoluteLength>::zero(),
size: Size::auto(),
min_size: Size::auto(),
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::zero(),
// Aligment
align_items: None,
align_self: None,
align_content: None,
justify_content: None,
// Flexbox
flex_direction: FlexDirection::Row,
flex_wrap: FlexWrap::NoWrap,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Length::Auto,
background: None,
border_color: None,
corner_radii: Corners::default(),
box_shadow: Default::default(),
text: TextStyleRefinement::default(),
z_index: None,
}
}
}
#[derive(Refineable, Clone, Default, Debug, PartialEq, Eq)]
#[refineable(debug)]
pub struct UnderlineStyle {
pub thickness: Pixels,
pub color: Option<Hsla>,
pub wavy: bool,
}
#[derive(Clone, Debug)]
pub enum Fill {
Color(Hsla),
}
impl Fill {
pub fn color(&self) -> Option<Hsla> {
match self {
Fill::Color(color) => Some(*color),
}
}
}
impl Default for Fill {
fn default() -> Self {
Self::Color(Hsla::default())
}
}
impl From<Hsla> for Fill {
fn from(color: Hsla) -> Self {
Self::Color(color)
}
}
impl From<TextStyle> for HighlightStyle {
fn from(other: TextStyle) -> Self {
Self::from(&other)
}
}
impl From<&TextStyle> for HighlightStyle {
fn from(other: &TextStyle) -> Self {
Self {
color: Some(other.color),
font_weight: Some(other.font_weight),
font_style: Some(other.font_style),
underline: other.underline.clone(),
fade_out: None,
}
}
}
impl HighlightStyle {
pub fn highlight(&mut self, other: HighlightStyle) {
match (self.color, other.color) {
(Some(self_color), Some(other_color)) => {
self.color = Some(Hsla::blend(other_color, self_color));
}
(None, Some(other_color)) => {
self.color = Some(other_color);
}
_ => {}
}
if other.font_weight.is_some() {
self.font_weight = other.font_weight;
}
if other.font_style.is_some() {
self.font_style = other.font_style;
}
if other.underline.is_some() {
self.underline = other.underline;
}
match (other.fade_out, self.fade_out) {
(Some(source_fade), None) => self.fade_out = Some(source_fade),
(Some(source_fade), Some(dest_fade)) => {
self.fade_out = Some((dest_fade * (1. + source_fade)).clamp(0., 1.));
}
_ => {}
}
}
}
impl From<Hsla> for HighlightStyle {
fn from(color: Hsla) -> Self {
Self {
color: Some(color),
..Default::default()
}
}
}
+542
View File
@@ -0,0 +1,542 @@
use crate::{
self as gpui2, hsla, point, px, relative, rems, AlignItems, DefiniteLength, Display, Fill,
FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString, StyleRefinement,
};
use crate::{BoxShadow, TextStyleRefinement};
use smallvec::smallvec;
pub trait Styled {
fn style(&mut self) -> &mut StyleRefinement;
gpui2_macros::style_helpers!();
/// Sets the size of the element to the full width and height.
fn full(mut self) -> Self
where
Self: Sized,
{
self.style().size.width = Some(relative(1.).into());
self.style().size.height = Some(relative(1.).into());
self
}
/// Sets the position of the element to `relative`.
/// [Docs](https://tailwindcss.com/docs/position)
fn relative(mut self) -> Self
where
Self: Sized,
{
self.style().position = Some(Position::Relative);
self
}
/// Sets the position of the element to `absolute`.
/// [Docs](https://tailwindcss.com/docs/position)
fn absolute(mut self) -> Self
where
Self: Sized,
{
self.style().position = Some(Position::Absolute);
self
}
/// Sets the display type of the element to `block`.
/// [Docs](https://tailwindcss.com/docs/display)
fn block(mut self) -> Self
where
Self: Sized,
{
self.style().display = Some(Display::Block);
self
}
/// Sets the display type of the element to `flex`.
/// [Docs](https://tailwindcss.com/docs/display)
fn flex(mut self) -> Self
where
Self: Sized,
{
self.style().display = Some(Display::Flex);
self
}
/// Sets the flex direction of the element to `column`.
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
fn flex_col(mut self) -> Self
where
Self: Sized,
{
self.style().flex_direction = Some(FlexDirection::Column);
self
}
/// Sets the flex direction of the element to `row`.
/// [Docs](https://tailwindcss.com/docs/flex-direction#row)
fn flex_row(mut self) -> Self
where
Self: Sized,
{
self.style().flex_direction = Some(FlexDirection::Row);
self
}
/// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
/// [Docs](https://tailwindcss.com/docs/flex#flex-1)
fn flex_1(mut self) -> Self
where
Self: Sized,
{
self.style().flex_grow = Some(1.);
self.style().flex_shrink = Some(1.);
self.style().flex_basis = Some(relative(0.).into());
self
}
/// Sets the element to allow a flex item to grow and shrink, taking into account its initial size.
/// [Docs](https://tailwindcss.com/docs/flex#auto)
fn flex_auto(mut self) -> Self
where
Self: Sized,
{
self.style().flex_grow = Some(1.);
self.style().flex_shrink = Some(1.);
self.style().flex_basis = Some(Length::Auto);
self
}
/// Sets the element to allow a flex item to shrink but not grow, taking into account its initial size.
/// [Docs](https://tailwindcss.com/docs/flex#initial)
fn flex_initial(mut self) -> Self
where
Self: Sized,
{
self.style().flex_grow = Some(0.);
self.style().flex_shrink = Some(1.);
self.style().flex_basis = Some(Length::Auto);
self
}
/// Sets the element to prevent a flex item from growing or shrinking.
/// [Docs](https://tailwindcss.com/docs/flex#none)
fn flex_none(mut self) -> Self
where
Self: Sized,
{
self.style().flex_grow = Some(0.);
self.style().flex_shrink = Some(0.);
self
}
/// Sets the element to allow a flex item to grow to fill any available space.
/// [Docs](https://tailwindcss.com/docs/flex-grow)
fn grow(mut self) -> Self
where
Self: Sized,
{
self.style().flex_grow = Some(1.);
self
}
fn items_start(mut self) -> Self
where
Self: Sized,
{
self.style().align_items = Some(AlignItems::FlexStart);
self
}
fn items_end(mut self) -> Self
where
Self: Sized,
{
self.style().align_items = Some(AlignItems::FlexEnd);
self
}
fn items_center(mut self) -> Self
where
Self: Sized,
{
self.style().align_items = Some(AlignItems::Center);
self
}
fn justify_between(mut self) -> Self
where
Self: Sized,
{
self.style().justify_content = Some(JustifyContent::SpaceBetween);
self
}
fn justify_center(mut self) -> Self
where
Self: Sized,
{
self.style().justify_content = Some(JustifyContent::Center);
self
}
fn justify_start(mut self) -> Self
where
Self: Sized,
{
self.style().justify_content = Some(JustifyContent::Start);
self
}
fn justify_end(mut self) -> Self
where
Self: Sized,
{
self.style().justify_content = Some(JustifyContent::End);
self
}
fn justify_around(mut self) -> Self
where
Self: Sized,
{
self.style().justify_content = Some(JustifyContent::SpaceAround);
self
}
fn bg<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.style().background = Some(fill.into());
self
}
fn border_color<C>(mut self, border_color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.style().border_color = Some(border_color.into());
self
}
fn shadow(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(1.)),
blur_radius: px(3.),
spread_radius: px(0.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(-1.),
}
]);
self
}
fn shadow_none(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(Default::default());
self
}
fn shadow_sm(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(smallvec::smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.05),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]);
self
}
fn shadow_md(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0.5, 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-1.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(2.)),
blur_radius: px(4.),
spread_radius: px(-2.),
}
]);
self
}
fn shadow_lg(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(10.)),
blur_radius: px(15.),
spread_radius: px(-3.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-4.),
}
]);
self
}
fn shadow_xl(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(20.)),
blur_radius: px(25.),
spread_radius: px(-5.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(8.)),
blur_radius: px(10.),
spread_radius: px(-6.),
}
]);
self
}
fn shadow_2xl(mut self) -> Self
where
Self: Sized,
{
self.style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.25),
offset: point(px(0.), px(25.)),
blur_radius: px(50.),
spread_radius: px(-12.),
}]);
self
}
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.style();
&mut style.text
}
fn text_color(mut self, color: impl Into<Hsla>) -> Self
where
Self: Sized,
{
self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
self
}
fn text_size(mut self, size: impl Into<Rems>) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(size.into());
self
}
fn text_xs(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(0.75));
self
}
fn text_sm(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(0.875));
self
}
fn text_base(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.0));
self
}
fn text_lg(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.125));
self
}
fn text_xl(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.25));
self
}
fn text_2xl(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.5));
self
}
fn text_3xl(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.875));
self
}
fn text_decoration_none(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.underline = None;
self
}
fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.color = Some(color.into());
self
}
fn text_decoration_solid(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.wavy = false;
self
}
fn text_decoration_wavy(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.wavy = true;
self
}
fn text_decoration_0(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(0.);
self
}
fn text_decoration_1(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(1.);
self
}
fn text_decoration_2(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(2.);
self
}
fn text_decoration_4(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(4.);
self
}
fn text_decoration_8(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(8.);
self
}
fn font(mut self, family_name: impl Into<SharedString>) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_family = Some(family_name.into());
self
}
fn line_height(mut self, line_height: impl Into<DefiniteLength>) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.line_height = Some(line_height.into());
self
}
}
+113
View File
@@ -0,0 +1,113 @@
use collections::{BTreeMap, BTreeSet};
use parking_lot::Mutex;
use std::{fmt::Debug, mem, sync::Arc};
use util::post_inc;
pub(crate) struct SubscriberSet<EmitterKey, Callback>(
Arc<Mutex<SubscriberSetState<EmitterKey, Callback>>>,
);
impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
fn clone(&self) -> Self {
SubscriberSet(self.0.clone())
}
}
struct SubscriberSetState<EmitterKey, Callback> {
subscribers: BTreeMap<EmitterKey, BTreeMap<usize, Callback>>,
dropped_subscribers: BTreeSet<(EmitterKey, usize)>,
next_subscriber_id: usize,
}
impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
where
EmitterKey: 'static + Ord + Clone + Debug,
Callback: 'static,
{
pub fn new() -> Self {
Self(Arc::new(Mutex::new(SubscriberSetState {
subscribers: Default::default(),
dropped_subscribers: Default::default(),
next_subscriber_id: 0,
})))
}
pub fn insert(&self, emitter: EmitterKey, callback: Callback) -> Subscription {
let mut lock = self.0.lock();
let subscriber_id = post_inc(&mut lock.next_subscriber_id);
lock.subscribers
.entry(emitter.clone())
.or_default()
.insert(subscriber_id, callback);
let this = self.0.clone();
Subscription {
unsubscribe: Some(Box::new(move || {
let mut lock = this.lock();
if let Some(subscribers) = lock.subscribers.get_mut(&emitter) {
subscribers.remove(&subscriber_id);
if subscribers.is_empty() {
lock.subscribers.remove(&emitter);
return;
}
}
// We didn't manage to remove the subscription, which means it was dropped
// while invoking the callback. Mark it as dropped so that we can remove it
// later.
lock.dropped_subscribers.insert((emitter, subscriber_id));
})),
}
}
pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator<Item = Callback> {
let subscribers = self.0.lock().subscribers.remove(&emitter);
subscribers.unwrap_or_default().into_values()
}
pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
where
F: FnMut(&mut Callback) -> bool,
{
let entry = self.0.lock().subscribers.remove_entry(emitter);
if let Some((emitter, mut subscribers)) = entry {
subscribers.retain(|_, callback| f(callback));
let mut lock = self.0.lock();
// Add any new subscribers that were added while invoking the callback.
if let Some(new_subscribers) = lock.subscribers.remove(&emitter) {
subscribers.extend(new_subscribers);
}
// Remove any dropped subscriptions that were dropped while invoking the callback.
for (dropped_emitter, dropped_subscription_id) in
mem::take(&mut lock.dropped_subscribers)
{
debug_assert_eq!(emitter, dropped_emitter);
subscribers.remove(&dropped_subscription_id);
}
if !subscribers.is_empty() {
lock.subscribers.insert(emitter, subscribers);
}
}
}
}
#[must_use]
pub struct Subscription {
unsubscribe: Option<Box<dyn FnOnce()>>,
}
impl Subscription {
pub fn detach(mut self) {
self.unsubscribe.take();
}
}
impl Drop for Subscription {
fn drop(&mut self) {
if let Some(unsubscribe) = self.unsubscribe.take() {
unsubscribe();
}
}
}
+47
View File
@@ -0,0 +1,47 @@
use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
use anyhow::anyhow;
use std::hash::Hash;
use std::sync::Arc;
#[derive(Clone, PartialEq, Hash, Eq)]
pub struct RenderSvgParams {
pub(crate) path: SharedString,
pub(crate) size: Size<DevicePixels>,
}
pub struct SvgRenderer {
asset_source: Arc<dyn AssetSource>,
}
impl SvgRenderer {
pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
Self { asset_source }
}
pub fn render(&self, params: &RenderSvgParams) -> Result<Vec<u8>> {
if params.size.is_zero() {
return Err(anyhow!("can't render at a zero size"));
}
// Load the tree.
let bytes = self.asset_source.load(&params.path)?;
let tree = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
// Render the SVG to a pixmap with the specified width and height.
let mut pixmap =
tiny_skia::Pixmap::new(params.size.width.into(), params.size.height.into()).unwrap();
resvg::render(
&tree,
usvg::FitTo::Width(params.size.width.into()),
pixmap.as_mut(),
);
// Convert the pixmap's pixels into an alpha mask.
let alpha_mask = pixmap
.pixels()
.iter()
.map(|p| p.alpha())
.collect::<Vec<_>>();
Ok(alpha_mask)
}
}
+435
View File
@@ -0,0 +1,435 @@
use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style};
use collections::HashMap;
use std::fmt::Debug;
use taffy::{
geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
style::AvailableSpace as TaffyAvailableSpace,
tree::{Measurable, MeasureFunc, NodeId},
Taffy,
};
pub struct TaffyLayoutEngine {
taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
}
static EXPECT_MESSAGE: &'static str =
"we should avoid taffy layout errors by construction if possible";
impl TaffyLayoutEngine {
pub fn new() -> Self {
TaffyLayoutEngine {
taffy: Taffy::new(),
children_to_parents: HashMap::default(),
absolute_layout_bounds: HashMap::default(),
}
}
pub fn request_layout(
&mut self,
style: &Style,
rem_size: Pixels,
children: &[LayoutId],
) -> LayoutId {
let style = style.to_taffy(rem_size);
if children.is_empty() {
self.taffy.new_leaf(style).expect(EXPECT_MESSAGE).into()
} else {
let parent_id = self
.taffy
// This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
.new_with_children(style, unsafe { std::mem::transmute(children) })
.expect(EXPECT_MESSAGE)
.into();
for child_id in children {
self.children_to_parents.insert(*child_id, parent_id);
}
parent_id
}
}
pub fn request_measured_layout(
&mut self,
style: Style,
rem_size: Pixels,
measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
+ Send
+ Sync
+ 'static,
) -> LayoutId {
let style = style.to_taffy(rem_size);
let measurable = Box::new(Measureable(measure)) as Box<dyn Measurable>;
self.taffy
.new_leaf_with_measure(style, MeasureFunc::Boxed(measurable))
.expect(EXPECT_MESSAGE)
.into()
}
// Used to understand performance
#[allow(dead_code)]
fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
let mut count = 0;
for child in self.taffy.children(parent.0)? {
// Count this child.
count += 1;
// Count all of this child's children.
count += self.count_all_children(LayoutId(child))?
}
Ok(count)
}
// Used to understand performance
#[allow(dead_code)]
fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
println!(
"{parent:?} at depth {depth} has {} children",
self.taffy.child_count(parent.0)?
);
let mut max_child_depth = 0;
for child in self.taffy.children(parent.0)? {
max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
}
Ok(depth + 1 + max_child_depth)
}
// Used to understand performance
#[allow(dead_code)]
fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
let mut edges = Vec::new();
for child in self.taffy.children(parent.0)? {
edges.push((parent, LayoutId(child)));
edges.extend(self.get_edges(LayoutId(child))?);
}
Ok(edges)
}
pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) {
// println!("Laying out {} children", self.count_all_children(id)?);
// println!("Max layout depth: {}", self.max_depth(0, id)?);
// Output the edges (branches) of the tree in Mermaid format for visualization.
// println!("Edges:");
// for (a, b) in self.get_edges(id)? {
// println!("N{} --> N{}", u64::from(a), u64::from(b));
// }
// println!("");
let started_at = std::time::Instant::now();
self.taffy
.compute_layout(id.into(), available_space.into())
.expect(EXPECT_MESSAGE);
println!("compute_layout took {:?}", started_at.elapsed());
}
pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds<Pixels> {
if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
return layout;
}
let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
let mut bounds = Bounds {
origin: layout.location.into(),
size: layout.size.into(),
};
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
let parent_bounds = self.layout_bounds(parent_id);
bounds.origin += parent_bounds.origin;
}
self.absolute_layout_bounds.insert(id, bounds);
bounds
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[repr(transparent)]
pub struct LayoutId(NodeId);
impl std::hash::Hash for LayoutId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
u64::from(self.0).hash(state);
}
}
impl From<NodeId> for LayoutId {
fn from(node_id: NodeId) -> Self {
Self(node_id)
}
}
impl From<LayoutId> for NodeId {
fn from(layout_id: LayoutId) -> NodeId {
layout_id.0
}
}
struct Measureable<F>(F);
impl<F> taffy::tree::Measurable for Measureable<F>
where
F: Send + Sync + Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>,
{
fn measure(
&self,
known_dimensions: TaffySize<Option<f32>>,
available_space: TaffySize<TaffyAvailableSpace>,
) -> TaffySize<f32> {
let known_dimensions: Size<Option<f32>> = known_dimensions.into();
let known_dimensions: Size<Option<Pixels>> = known_dimensions.map(|d| d.map(Into::into));
let available_space = available_space.into();
let size = (self.0)(known_dimensions, available_space);
size.into()
}
}
trait ToTaffy<Output> {
fn to_taffy(&self, rem_size: Pixels) -> Output;
}
impl ToTaffy<taffy::style::Style> for Style {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
taffy::style::Style {
display: self.display,
overflow: self.overflow.clone().into(),
scrollbar_width: self.scrollbar_width,
position: self.position,
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
max_size: self.max_size.to_taffy(rem_size),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border_widths.to_taffy(rem_size),
align_items: self.align_items,
align_self: self.align_self,
align_content: self.align_content,
justify_content: self.justify_content,
gap: self.gap.to_taffy(rem_size),
flex_direction: self.flex_direction,
flex_wrap: self.flex_wrap,
flex_basis: self.flex_basis.to_taffy(rem_size),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
}
}
}
// impl ToTaffy for Bounds<Length> {
// type Output = taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto>;
// fn to_taffy(
// &self,
// rem_size: Pixels,
// ) -> taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto> {
// taffy::prelude::Bounds {
// origin: self.origin.to_taffy(rem_size),
// size: self.size.to_taffy(rem_size),
// }
// }
// }
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
match self {
Length::Definite(length) => length.to_taffy(rem_size),
Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
}
}
}
impl ToTaffy<taffy::style::Dimension> for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
match self {
Length::Definite(length) => length.to_taffy(rem_size),
Length::Auto => taffy::prelude::Dimension::Auto,
}
}
}
impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => {
taffy::style::LengthPercentage::Length(pixels.into())
}
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => {
taffy::style::LengthPercentage::Percent(*fraction)
}
}
}
}
impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => {
taffy::style::LengthPercentageAuto::Length(pixels.into())
}
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentageAuto::Length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => {
taffy::style::LengthPercentageAuto::Percent(*fraction)
}
}
}
}
impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::Length(pixels.into()),
AbsoluteLength::Rems(rems) => {
taffy::style::Dimension::Length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => taffy::style::Dimension::Percent(*fraction),
}
}
}
impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
match self {
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(pixels.into()),
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
}
}
}
}
impl<T, T2> From<TaffyPoint<T>> for Point<T2>
where
T: Into<T2>,
T2: Clone + Default + Debug,
{
fn from(point: TaffyPoint<T>) -> Point<T2> {
Point {
x: point.x.into(),
y: point.y.into(),
}
}
}
impl<T, T2> Into<TaffyPoint<T2>> for Point<T>
where
T: Into<T2> + Clone + Default + Debug,
{
fn into(self) -> TaffyPoint<T2> {
TaffyPoint {
x: self.x.into(),
y: self.y.into(),
}
}
}
impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
where
T: ToTaffy<U> + Clone + Default + Debug,
{
fn to_taffy(&self, rem_size: Pixels) -> TaffySize<U> {
TaffySize {
width: self.width.to_taffy(rem_size).into(),
height: self.height.to_taffy(rem_size).into(),
}
}
}
impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
where
T: ToTaffy<U> + Clone + Default + Debug,
{
fn to_taffy(&self, rem_size: Pixels) -> TaffyRect<U> {
TaffyRect {
top: self.top.to_taffy(rem_size).into(),
right: self.right.to_taffy(rem_size).into(),
bottom: self.bottom.to_taffy(rem_size).into(),
left: self.left.to_taffy(rem_size).into(),
}
}
}
impl<T, U> From<TaffySize<T>> for Size<U>
where
T: Into<U>,
U: Clone + Default + Debug,
{
fn from(taffy_size: TaffySize<T>) -> Self {
Size {
width: taffy_size.width.into(),
height: taffy_size.height.into(),
}
}
}
impl<T, U> From<Size<T>> for TaffySize<U>
where
T: Into<U> + Clone + Default + Debug,
{
fn from(size: Size<T>) -> Self {
TaffySize {
width: size.width.into(),
height: size.height.into(),
}
}
}
#[derive(Copy, Clone, Default, Debug)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Definite(Pixels),
/// The amount of space available is indefinite and the node should be laid out under a min-content constraint
#[default]
MinContent,
/// The amount of space available is indefinite and the node should be laid out under a max-content constraint
MaxContent,
}
impl From<AvailableSpace> for TaffyAvailableSpace {
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
match space {
AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
}
}
}
impl From<TaffyAvailableSpace> for AvailableSpace {
fn from(space: TaffyAvailableSpace) -> AvailableSpace {
match space {
TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
}
}
}
impl From<Pixels> for AvailableSpace {
fn from(pixels: Pixels) -> Self {
AvailableSpace::Definite(pixels)
}
}
+533
View File
@@ -0,0 +1,533 @@
mod font_features;
mod line;
mod line_layout;
mod line_wrapper;
use anyhow::anyhow;
pub use font_features::*;
pub use line::*;
pub use line_layout::*;
use line_wrapper::*;
use smallvec::SmallVec;
use crate::{
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
UnderlineStyle,
};
use collections::HashMap;
use core::fmt;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use std::{
cmp,
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
sync::Arc,
};
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
#[repr(C)]
pub struct FontId(pub usize);
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub struct FontFamilyId(pub usize);
pub const SUBPIXEL_VARIANTS: u8 = 4;
pub struct TextSystem {
line_layout_cache: Arc<LineLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>,
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
}
impl TextSystem {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
TextSystem {
line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
platform_text_system,
font_metrics: RwLock::new(HashMap::default()),
font_ids_by_font: RwLock::new(HashMap::default()),
wrapper_pool: Mutex::new(HashMap::default()),
font_runs_pool: Default::default(),
}
}
pub fn font_id(&self, font: &Font) -> Result<FontId> {
let font_id = self.font_ids_by_font.read().get(font).copied();
if let Some(font_id) = font_id {
Ok(font_id)
} else {
let font_id = self.platform_text_system.font_id(font)?;
self.font_ids_by_font.write().insert(font.clone(), font_id);
Ok(font_id)
}
}
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result<Bounds<Pixels>> {
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
}
pub fn typographic_bounds(
&self,
font_id: FontId,
font_size: Pixels,
character: char,
) -> Result<Bounds<Pixels>> {
let glyph_id = self
.platform_text_system
.glyph_for_char(font_id, character)
.ok_or_else(|| anyhow!("glyph not found for character '{}'", character))?;
let bounds = self
.platform_text_system
.typographic_bounds(font_id, glyph_id)?;
self.read_metrics(font_id, |metrics| {
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
})
}
pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
let glyph_id = self
.platform_text_system
.glyph_for_char(font_id, ch)
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
let result = self.platform_text_system.advance(font_id, glyph_id)?
/ self.units_per_em(font_id)? as f32;
Ok(result * font_size)
}
pub fn units_per_em(&self, font_id: FontId) -> Result<u32> {
self.read_metrics(font_id, |metrics| metrics.units_per_em as u32)
}
pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
}
pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
}
pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
}
pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font_id, |metrics| metrics.descent(font_size))
}
pub fn baseline_offset(
&self,
font_id: FontId,
font_size: Pixels,
line_height: Pixels,
) -> Result<Pixels> {
let ascent = self.ascent(font_id, font_size)?;
let descent = self.descent(font_id, font_size)?;
let padding_top = (line_height - ascent - descent) / 2.;
Ok(padding_top + ascent)
}
fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
let lock = self.font_metrics.upgradable_read();
if let Some(metrics) = lock.get(&font_id) {
Ok(read(metrics))
} else {
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
let metrics = lock
.entry(font_id)
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
Ok(read(metrics))
}
}
pub fn layout_text(
&self,
text: &SharedString,
font_size: Pixels,
runs: &[TextRun],
wrap_width: Option<Pixels>,
) -> Result<SmallVec<[Line; 1]>> {
let mut runs = runs.iter().cloned().peekable();
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
let mut lines = SmallVec::new();
let mut line_start = 0;
for line_text in text.split('\n') {
let line_text = SharedString::from(line_text.to_string());
let line_end = line_start + line_text.len();
let mut last_font: Option<Font> = None;
let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
let mut run_start = line_start;
while run_start < line_end {
let Some(run) = runs.peek_mut() else {
break;
};
let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
if last_font == Some(run.font.clone()) {
font_runs.last_mut().unwrap().len += run_len_within_line;
} else {
last_font = Some(run.font.clone());
font_runs.push(FontRun {
len: run_len_within_line,
font_id: self.platform_text_system.font_id(&run.font)?,
});
}
if decoration_runs.last().map_or(false, |last_run| {
last_run.color == run.color && last_run.underline == run.underline
}) {
decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
} else {
decoration_runs.push(DecorationRun {
len: run_len_within_line as u32,
color: run.color,
underline: run.underline.clone(),
});
}
if run_len_within_line == run.len {
runs.next();
} else {
// Preserve the remainder of the run for the next line
run.len -= run_len_within_line;
}
run_start += run_len_within_line;
}
let layout = self
.line_layout_cache
.layout_line(&line_text, font_size, &font_runs, wrap_width);
lines.push(Line {
layout,
decorations: decoration_runs,
});
line_start = line_end + 1; // Skip `\n` character.
font_runs.clear();
}
self.font_runs_pool.lock().push(font_runs);
Ok(lines)
}
pub fn start_frame(&self) {
self.line_layout_cache.start_frame()
}
pub fn line_wrapper(
self: &Arc<Self>,
font: Font,
font_size: Pixels,
) -> Result<LineWrapperHandle> {
let lock = &mut self.wrapper_pool.lock();
let font_id = self.font_id(&font)?;
let wrappers = lock
.entry(FontIdWithSize { font_id, font_size })
.or_default();
let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| {
Ok(LineWrapper::new(
font_id,
font_size,
self.platform_text_system.clone(),
))
})?;
Ok(LineWrapperHandle {
wrapper: Some(wrapper),
text_system: self.clone(),
})
}
pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
self.platform_text_system.glyph_raster_bounds(params)
}
pub fn rasterize_glyph(
&self,
glyph_id: &RenderGlyphParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
self.platform_text_system.rasterize_glyph(glyph_id)
}
}
#[derive(Hash, Eq, PartialEq)]
struct FontIdWithSize {
font_id: FontId,
font_size: Pixels,
}
pub struct LineWrapperHandle {
wrapper: Option<LineWrapper>,
text_system: Arc<TextSystem>,
}
impl Drop for LineWrapperHandle {
fn drop(&mut self) {
let mut state = self.text_system.wrapper_pool.lock();
let wrapper = self.wrapper.take().unwrap();
state
.get_mut(&FontIdWithSize {
font_id: wrapper.font_id.clone(),
font_size: wrapper.font_size,
})
.unwrap()
.push(wrapper);
}
}
impl Deref for LineWrapperHandle {
type Target = LineWrapper;
fn deref(&self) -> &Self::Target {
self.wrapper.as_ref().unwrap()
}
}
impl DerefMut for LineWrapperHandle {
fn deref_mut(&mut self) -> &mut Self::Target {
self.wrapper.as_mut().unwrap()
}
}
/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
/// with 400.0 as normal.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct FontWeight(pub f32);
impl Default for FontWeight {
#[inline]
fn default() -> FontWeight {
FontWeight::NORMAL
}
}
impl Hash for FontWeight {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
}
}
impl Eq for FontWeight {}
impl FontWeight {
/// Thin weight (100), the thinnest value.
pub const THIN: FontWeight = FontWeight(100.0);
/// Extra light weight (200).
pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
/// Light weight (300).
pub const LIGHT: FontWeight = FontWeight(300.0);
/// Normal (400).
pub const NORMAL: FontWeight = FontWeight(400.0);
/// Medium weight (500, higher than normal).
pub const MEDIUM: FontWeight = FontWeight(500.0);
/// Semibold weight (600).
pub const SEMIBOLD: FontWeight = FontWeight(600.0);
/// Bold weight (700).
pub const BOLD: FontWeight = FontWeight(700.0);
/// Extra-bold weight (800).
pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
/// Black weight (900), the thickest value.
pub const BLACK: FontWeight = FontWeight(900.0);
}
/// Allows italic or oblique faces to be selected.
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub enum FontStyle {
/// A face that is neither italic not obliqued.
Normal,
/// A form that is generally cursive in nature.
Italic,
/// A typically-sloped version of the regular face.
Oblique,
}
impl Default for FontStyle {
fn default() -> FontStyle {
FontStyle::Normal
}
}
impl Display for FontStyle {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self, f)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextRun {
pub len: usize,
pub font: Font,
pub color: Hsla,
pub underline: Option<UnderlineStyle>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct GlyphId(u32);
impl From<GlyphId> for u32 {
fn from(value: GlyphId) -> Self {
value.0
}
}
impl From<u16> for GlyphId {
fn from(num: u16) -> Self {
GlyphId(num as u32)
}
}
impl From<u32> for GlyphId {
fn from(num: u32) -> Self {
GlyphId(num)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderGlyphParams {
pub(crate) font_id: FontId,
pub(crate) glyph_id: GlyphId,
pub(crate) font_size: Pixels,
pub(crate) subpixel_variant: Point<u8>,
pub(crate) scale_factor: f32,
pub(crate) is_emoji: bool,
}
impl Eq for RenderGlyphParams {}
impl Hash for RenderGlyphParams {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_id.0.hash(state);
self.glyph_id.0.hash(state);
self.font_size.0.to_bits().hash(state);
self.subpixel_variant.hash(state);
self.scale_factor.to_bits().hash(state);
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderEmojiParams {
pub(crate) font_id: FontId,
pub(crate) glyph_id: GlyphId,
pub(crate) font_size: Pixels,
pub(crate) scale_factor: f32,
}
impl Eq for RenderEmojiParams {}
impl Hash for RenderEmojiParams {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_id.0.hash(state);
self.glyph_id.0.hash(state);
self.font_size.0.to_bits().hash(state);
self.scale_factor.to_bits().hash(state);
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Font {
pub family: SharedString,
pub features: FontFeatures,
pub weight: FontWeight,
pub style: FontStyle,
}
pub fn font(family: impl Into<SharedString>) -> Font {
Font {
family: family.into(),
features: FontFeatures::default(),
weight: FontWeight::default(),
style: FontStyle::default(),
}
}
impl Font {
pub fn bold(mut self) -> Self {
self.weight = FontWeight::BOLD;
self
}
}
/// A struct for storing font metrics.
/// It is used to define the measurements of a typeface.
#[derive(Clone, Copy, Debug)]
pub struct FontMetrics {
/// The number of font units that make up the "em square",
/// a scalable grid for determining the size of a typeface.
pub(crate) units_per_em: u32,
/// The vertical distance from the baseline of the font to the top of the glyph covers.
pub(crate) ascent: f32,
/// The vertical distance from the baseline of the font to the bottom of the glyph covers.
pub(crate) descent: f32,
/// The recommended additional space to add between lines of type.
pub(crate) line_gap: f32,
/// The suggested position of the underline.
pub(crate) underline_position: f32,
/// The suggested thickness of the underline.
pub(crate) underline_thickness: f32,
/// The height of a capital letter measured from the baseline of the font.
pub(crate) cap_height: f32,
/// The height of a lowercase x.
pub(crate) x_height: f32,
/// The outer limits of the area that the font covers.
pub(crate) bounding_box: Bounds<f32>,
}
impl FontMetrics {
/// Returns the vertical distance from the baseline of the font to the top of the glyph covers in pixels.
pub fn ascent(&self, font_size: Pixels) -> Pixels {
Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
}
/// Returns the vertical distance from the baseline of the font to the bottom of the glyph covers in pixels.
pub fn descent(&self, font_size: Pixels) -> Pixels {
Pixels((self.descent / self.units_per_em as f32) * font_size.0)
}
/// Returns the recommended additional space to add between lines of type in pixels.
pub fn line_gap(&self, font_size: Pixels) -> Pixels {
Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
}
/// Returns the suggested position of the underline in pixels.
pub fn underline_position(&self, font_size: Pixels) -> Pixels {
Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
}
/// Returns the suggested thickness of the underline in pixels.
pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
}
/// Returns the height of a capital letter measured from the baseline of the font in pixels.
pub fn cap_height(&self, font_size: Pixels) -> Pixels {
Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
}
/// Returns the height of a lowercase x in pixels.
pub fn x_height(&self, font_size: Pixels) -> Pixels {
Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
}
/// Returns the outer limits of the area that the font covers in pixels.
pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
(self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
}
}
@@ -0,0 +1,162 @@
use schemars::{
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
macro_rules! create_definitions {
($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontFeatures {
enabled: u64,
disabled: u64,
}
impl FontFeatures {
$(
pub fn $name(&self) -> Option<bool> {
if (self.enabled & (1 << $idx)) != 0 {
Some(true)
} else if (self.disabled & (1 << $idx)) != 0 {
Some(false)
} else {
None
}
}
)*
}
impl std::fmt::Debug for FontFeatures {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("FontFeatures");
$(
if let Some(value) = self.$name() {
debug.field(stringify!($name), &value);
};
)*
debug.finish()
}
}
impl<'de> serde::Deserialize<'de> for FontFeatures {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{MapAccess, Visitor};
use std::fmt;
struct FontFeaturesVisitor;
impl<'de> Visitor<'de> for FontFeaturesVisitor {
type Value = FontFeatures;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map of font features")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut enabled: u64 = 0;
let mut disabled: u64 = 0;
while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
let idx = match key.as_str() {
$(stringify!($name) => $idx,)*
_ => continue,
};
match value {
Some(true) => enabled |= 1 << idx,
Some(false) => disabled |= 1 << idx,
None => {}
};
}
Ok(FontFeatures { enabled, disabled })
}
}
let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
Ok(features)
}
}
impl serde::Serialize for FontFeatures {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
$(
let feature = stringify!($name);
if let Some(value) = self.$name() {
map.serialize_entry(feature, &value)?;
}
)*
map.end()
}
}
impl JsonSchema for FontFeatures {
fn schema_name() -> String {
"FontFeatures".into()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
let mut schema = SchemaObject::default();
let properties = &mut schema.object().properties;
let feature_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
..Default::default()
});
$(
properties.insert(stringify!($name).to_owned(), feature_schema.clone());
)*
schema.into()
}
}
};
}
create_definitions!(
(calt, 0),
(case, 1),
(cpsp, 2),
(frac, 3),
(liga, 4),
(onum, 5),
(ordn, 6),
(pnum, 7),
(ss01, 8),
(ss02, 9),
(ss03, 10),
(ss04, 11),
(ss05, 12),
(ss06, 13),
(ss07, 14),
(ss08, 15),
(ss09, 16),
(ss10, 17),
(ss11, 18),
(ss12, 19),
(ss13, 20),
(ss14, 21),
(ss15, 22),
(ss16, 23),
(ss17, 24),
(ss18, 25),
(ss19, 26),
(ss20, 27),
(subs, 28),
(sups, 29),
(swsh, 30),
(titl, 31),
(tnum, 32),
(zero, 33)
);
+154
View File
@@ -0,0 +1,154 @@
use crate::{
black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
};
use smallvec::SmallVec;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct DecorationRun {
pub len: u32,
pub color: Hsla,
pub underline: Option<UnderlineStyle>,
}
#[derive(Clone, Default, Debug)]
pub struct Line {
pub(crate) layout: Arc<WrappedLineLayout>,
pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
}
impl Line {
pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
size(
self.layout.width,
line_height * (self.layout.wrap_boundaries.len() + 1),
)
}
pub fn wrap_count(&self) -> usize {
self.layout.wrap_boundaries.len()
}
pub fn paint(
&self,
origin: Point<Pixels>,
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
let padding_top =
(line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
let mut style_runs = self.decorations.iter();
let mut wraps = self.layout.wrap_boundaries.iter().peekable();
let mut run_end = 0;
let mut color = black();
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let text_system = cx.text_system().clone();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
let max_glyph_size = text_system
.bounding_box(run.font_id, self.layout.layout.font_size)?
.size;
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
wraps.next();
if let Some((underline_origin, underline_style)) = current_underline.take() {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
glyph_origin.x = origin.x;
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
let glyph_origin = glyph_origin + baseline_offset;
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 current_underline {
if style_run.underline.as_ref() != Some(underline_style) {
finished_underline = current_underline.take();
}
}
if let Some(run_underline) = style_run.underline.as_ref() {
current_underline.get_or_insert((
point(
glyph_origin.x,
origin.y
+ baseline_offset.y
+ (self.layout.layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
thickness: run_underline.thickness,
wavy: run_underline.wavy,
},
));
}
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = self.layout.text.len();
finished_underline = current_underline.take();
}
}
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
let max_glyph_bounds = Bounds {
origin: glyph_origin,
size: max_glyph_size,
};
let content_mask = cx.content_mask();
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin,
run.font_id,
glyph.id,
self.layout.layout.font_size,
)?;
} else {
cx.paint_glyph(
glyph_origin,
run.font_id,
glyph.id,
self.layout.layout.font_size,
color,
)?;
}
}
}
}
if let Some((underline_start, underline_style)) = current_underline.take() {
let line_end_x = origin.x + self.layout.layout.width;
cx.paint_underline(
underline_start,
line_end_x - underline_start.x,
&underline_style,
)?;
}
Ok(())
}
}
+295
View File
@@ -0,0 +1,295 @@
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
use derive_more::{Deref, DerefMut};
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
collections::HashMap,
hash::{Hash, Hasher},
sync::Arc,
};
#[derive(Default, Debug)]
pub struct LineLayout {
pub font_size: Pixels,
pub width: Pixels,
pub ascent: Pixels,
pub descent: Pixels,
pub runs: Vec<ShapedRun>,
}
#[derive(Debug)]
pub struct ShapedRun {
pub font_id: FontId,
pub glyphs: SmallVec<[ShapedGlyph; 8]>,
}
#[derive(Clone, Debug)]
pub struct ShapedGlyph {
pub id: GlyphId,
pub position: Point<Pixels>,
pub index: usize,
pub is_emoji: bool,
}
impl LineLayout {
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
if x >= self.width {
None
} else {
for run in self.runs.iter().rev() {
for glyph in run.glyphs.iter().rev() {
if glyph.position.x <= x {
return Some(glyph.index);
}
}
}
Some(0)
}
}
pub fn x_for_index(&self, index: usize) -> Pixels {
for run in &self.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return glyph.position.x;
}
}
}
self.width
}
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
for run in &self.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return Some(run.font_id);
}
}
}
None
}
fn compute_wrap_boundaries(
&self,
text: &str,
wrap_width: Pixels,
) -> SmallVec<[WrapBoundary; 1]> {
let mut boundaries = SmallVec::new();
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = px(0.);
let mut last_boundary = WrapBoundary {
run_ix: 0,
glyph_ix: 0,
};
let mut last_boundary_x = px(0.);
let mut prev_ch = '\0';
let mut glyphs = self
.runs
.iter()
.enumerate()
.flat_map(move |(run_ix, run)| {
run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
let character = text[glyph.index..].chars().next().unwrap();
(
WrapBoundary { run_ix, glyph_ix },
character,
glyph.position.x,
)
})
})
.peekable();
while let Some((boundary, ch, x)) = glyphs.next() {
if ch == '\n' {
continue;
}
if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = Some(boundary);
last_candidate_x = x;
}
if ch != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(boundary);
}
let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
let width = next_x - last_boundary_x;
if width > wrap_width && boundary > last_boundary {
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_boundary = last_candidate_ix;
last_boundary_x = last_candidate_x;
} else {
last_boundary = boundary;
last_boundary_x = x;
}
boundaries.push(last_boundary);
}
prev_ch = ch;
}
boundaries
}
}
#[derive(Deref, DerefMut, Default, Debug)]
pub struct WrappedLineLayout {
#[deref]
#[deref_mut]
pub layout: LineLayout,
pub text: SharedString,
pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct WrapBoundary {
pub run_ix: usize,
pub glyph_ix: usize,
}
pub(crate) struct LineLayoutCache {
prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
platform_text_system: Arc<dyn PlatformTextSystem>,
}
impl LineLayoutCache {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
Self {
prev_frame: Mutex::new(HashMap::new()),
curr_frame: RwLock::new(HashMap::new()),
platform_text_system,
}
}
pub fn start_frame(&self) {
let mut prev_frame = self.prev_frame.lock();
let mut curr_frame = self.curr_frame.write();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
curr_frame.clear();
}
pub fn layout_line(
&self,
text: &SharedString,
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
) -> Arc<WrappedLineLayout> {
let key = &CacheKeyRef {
text,
font_size,
runs,
wrap_width,
} as &dyn AsCacheKeyRef;
let curr_frame = self.curr_frame.upgradable_read();
if let Some(layout) = curr_frame.get(key) {
return layout.clone();
}
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
curr_frame.insert(key, layout.clone());
layout
} else {
let layout = self.platform_text_system.layout_line(text, font_size, runs);
let wrap_boundaries = wrap_width
.map(|wrap_width| layout.compute_wrap_boundaries(text.as_ref(), wrap_width))
.unwrap_or_default();
let wrapped_line = Arc::new(WrappedLineLayout {
layout,
text: text.clone(),
wrap_boundaries,
});
let key = CacheKey {
text: text.clone(),
font_size,
runs: SmallVec::from(runs),
wrap_width,
};
curr_frame.insert(key, wrapped_line.clone());
wrapped_line
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontRun {
pub(crate) len: usize,
pub(crate) font_id: FontId,
}
trait AsCacheKeyRef {
fn as_cache_key_ref(&self) -> CacheKeyRef;
}
#[derive(Eq)]
struct CacheKey {
text: SharedString,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: Pixels,
runs: &'a [FontRun],
wrap_width: Option<Pixels>,
}
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
self.as_cache_key_ref() == other.as_cache_key_ref()
}
}
impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state)
}
}
impl AsCacheKeyRef for CacheKey {
fn as_cache_key_ref(&self) -> CacheKeyRef {
CacheKeyRef {
text: &self.text,
font_size: self.font_size,
runs: self.runs.as_slice(),
wrap_width: self.wrap_width,
}
}
}
impl PartialEq for CacheKey {
fn eq(&self, other: &Self) -> bool {
self.as_cache_key_ref().eq(&other.as_cache_key_ref())
}
}
impl Hash for CacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state);
}
}
impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
self as &dyn AsCacheKeyRef
}
}
impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
fn as_cache_key_ref(&self) -> CacheKeyRef {
*self
}
}
@@ -0,0 +1,278 @@
use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem};
use collections::HashMap;
use std::{iter, sync::Arc};
pub struct LineWrapper {
platform_text_system: Arc<dyn PlatformTextSystem>,
pub(crate) font_id: FontId,
pub(crate) font_size: Pixels,
cached_ascii_char_widths: [Option<Pixels>; 128],
cached_other_char_widths: HashMap<char, Pixels>,
}
impl LineWrapper {
pub const MAX_INDENT: u32 = 256;
pub fn new(
font_id: FontId,
font_size: Pixels,
text_system: Arc<dyn PlatformTextSystem>,
) -> Self {
Self {
platform_text_system: text_system,
font_id,
font_size,
cached_ascii_char_widths: [None; 128],
cached_other_char_widths: HashMap::default(),
}
}
pub fn wrap_line<'a>(
&'a mut self,
line: &'a str,
wrap_width: Pixels,
) -> impl Iterator<Item = Boundary> + 'a {
let mut width = px(0.);
let mut first_non_whitespace_ix = None;
let mut indent = None;
let mut last_candidate_ix = 0;
let mut last_candidate_width = px(0.);
let mut last_wrap_ix = 0;
let mut prev_c = '\0';
let mut char_indices = line.char_indices();
iter::from_fn(move || {
for (ix, c) in char_indices.by_ref() {
if c == '\n' {
continue;
}
if 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
})
}
#[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 {
let mut buffer = [0; 4];
let buffer = c.encode_utf8(&mut buffer);
self.platform_text_system
.layout_line(
buffer,
self.font_size,
&[FontRun {
len: 1,
font_id: self.font_id,
}],
)
.width
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Boundary {
pub ix: usize,
pub next_indent: u32,
}
impl Boundary {
fn new(ix: usize, next_indent: u32) -> Self {
Self { ix, next_indent }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{font, App};
#[test]
fn test_wrap_line() {
App::test().run(|cx| {
let text_system = cx.text_system().clone();
let mut wrapper = LineWrapper::new(
text_system.font_id(&font("Courier")).unwrap(),
px(16.),
text_system.platform_text_system.clone(),
);
assert_eq!(
wrapper
.wrap_line("aa bbb cccc ddddd eeee", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(12, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
.collect::<Vec<_>>(),
&[
Boundary::new(4, 0),
Boundary::new(11, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaa", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 5),
Boundary::new(9, 5),
Boundary::new(11, 5),
]
);
assert_eq!(
wrapper
.wrap_line(" ", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 0),
Boundary::new(21, 0)
]
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaaaaaaaaa", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 3),
Boundary::new(18, 3),
Boundary::new(22, 3),
]
);
});
}
// todo!("move this to a test on TextSystem::layout_text")
// todo! repeat this test
// #[test]
// fn test_wrap_shaped_line() {
// App::test().run(|cx| {
// let text_system = cx.text_system().clone();
// let normal = TextRun {
// len: 0,
// font: font("Helvetica"),
// color: Default::default(),
// underline: Default::default(),
// };
// let bold = TextRun {
// len: 0,
// font: font("Helvetica").bold(),
// color: Default::default(),
// underline: Default::default(),
// };
// impl TextRun {
// fn with_len(&self, len: usize) -> Self {
// let mut this = self.clone();
// this.len = len;
// this
// }
// }
// let text = "aa bbb cccc ddddd eeee".into();
// let lines = text_system
// .layout_text(
// &text,
// px(16.),
// &[
// normal.with_len(4),
// bold.with_len(5),
// normal.with_len(6),
// bold.with_len(1),
// normal.with_len(7),
// ],
// None,
// )
// .unwrap();
// let line = &lines[0];
// let mut wrapper = LineWrapper::new(
// text_system.font_id(&normal.font).unwrap(),
// px(16.),
// text_system.platform_text_system.clone(),
// );
// assert_eq!(
// wrapper
// .wrap_shaped_line(&text, &line, px(72.))
// .collect::<Vec<_>>(),
// &[
// ShapedBoundary {
// run_ix: 1,
// glyph_ix: 3
// },
// ShapedBoundary {
// run_ix: 2,
// glyph_ix: 3
// },
// ShapedBoundary {
// run_ix: 4,
// glyph_ix: 2
// }
// ],
// );
// });
// }
}
+41
View File
@@ -0,0 +1,41 @@
pub use util::*;
// pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
// where
// F: Future<Output = T>,
// {
// let timer = async {
// smol::Timer::after(timeout).await;
// Err(())
// };
// let future = async move { Ok(f.await) };
// timer.race(future).await
// }
#[cfg(any(test, feature = "test"))]
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
#[cfg(any(test, feature = "test"))]
impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use backtrace::{BacktraceFmt, BytesOrWideString};
let cwd = std::env::current_dir().unwrap();
let cwd = cwd.parent().unwrap();
let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
std::fmt::Display::fmt(&path, fmt)
};
let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
for frame in self.0.frames() {
let mut formatted_frame = fmt.frame();
if frame
.symbols()
.iter()
.any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
{
formatted_frame.backtrace_frame(frame)?;
}
}
fmt.finish()
}
}
+307
View File
@@ -0,0 +1,307 @@
use parking_lot::Mutex;
use crate::{
AnyBox, AnyElement, BorrowWindow, Bounds, Element, ElementId, EntityId, Handle, IntoAnyElement,
LayoutId, Pixels, ViewContext, WindowContext,
};
use std::{marker::PhantomData, sync::Arc};
pub struct View<V: Send + Sync> {
state: Handle<V>,
render: Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyElement<V> + Send + Sync + 'static>,
}
impl<V: 'static + Send + Sync> View<V> {
pub fn into_any(self) -> AnyView {
AnyView {
view: Arc::new(Mutex::new(self)),
}
}
}
impl<V: Send + Sync> Clone for View<V> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
render: self.render.clone(),
}
}
}
pub fn view<V, E>(
state: Handle<V>,
render: impl Fn(&mut V, &mut ViewContext<V>) -> E + Send + Sync + 'static,
) -> View<V>
where
E: IntoAnyElement<V>,
V: 'static + Send + Sync,
{
View {
state,
render: Arc::new(move |state, cx| render(state, cx).into_any()),
}
}
impl<V: 'static + Send + Sync, ParentViewState: 'static + Send + Sync>
IntoAnyElement<ParentViewState> for View<V>
{
fn into_any(self) -> AnyElement<ParentViewState> {
AnyElement::new(EraseViewState {
view: self,
parent_view_state_type: PhantomData,
})
}
}
impl<V: 'static + Send + Sync> Element for View<V> {
type ViewState = ();
type ElementState = AnyElement<V>;
fn id(&self) -> Option<crate::ElementId> {
Some(ElementId::View(self.state.id))
}
fn initialize(
&mut self,
_: &mut (),
_: Option<Self::ElementState>,
cx: &mut ViewContext<()>,
) -> Self::ElementState {
self.state.update(cx, |state, cx| {
let mut any_element = (self.render)(state, cx);
any_element.initialize(state, cx);
any_element
})
}
fn layout(
&mut self,
_: &mut (),
element: &mut Self::ElementState,
cx: &mut ViewContext<()>,
) -> LayoutId {
self.state.update(cx, |state, cx| element.layout(state, cx))
}
fn paint(
&mut self,
_: Bounds<Pixels>,
_: &mut (),
element: &mut Self::ElementState,
cx: &mut ViewContext<()>,
) {
self.state.update(cx, |state, cx| element.paint(state, cx))
}
}
struct EraseViewState<V: 'static + Send + Sync, ParentV> {
view: View<V>,
parent_view_state_type: PhantomData<ParentV>,
}
impl<V, ParentV> IntoAnyElement<ParentV> for EraseViewState<V, ParentV>
where
V: 'static + Send + Sync,
ParentV: 'static + Send + Sync,
{
fn into_any(self) -> AnyElement<ParentV> {
AnyElement::new(self)
}
}
impl<V, ParentV> Element for EraseViewState<V, ParentV>
where
V: 'static + Send + Sync,
ParentV: 'static + Send + Sync,
{
type ViewState = ParentV;
type ElementState = AnyBox;
fn id(&self) -> Option<crate::ElementId> {
Element::id(&self.view)
}
fn initialize(
&mut self,
_: &mut Self::ViewState,
_: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> Self::ElementState {
ViewObject::initialize(&mut self.view, cx)
}
fn layout(
&mut self,
_: &mut Self::ViewState,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId {
ViewObject::layout(&mut self.view, element, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::ViewState,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) {
ViewObject::paint(&mut self.view, bounds, element, cx)
}
}
trait ViewObject: 'static + Send + Sync {
fn entity_id(&self) -> EntityId;
fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox;
fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
fn paint(&mut self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
}
impl<V: Send + Sync + 'static> ViewObject for View<V> {
fn entity_id(&self) -> EntityId {
self.state.id
}
fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox {
cx.with_element_id(self.entity_id(), |_global_id, cx| {
self.state.update(cx, |state, cx| {
let mut any_element = Box::new((self.render)(state, cx));
any_element.initialize(state, cx);
any_element as AnyBox
})
})
}
fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
cx.with_element_id(self.entity_id(), |_global_id, cx| {
self.state.update(cx, |state, cx| {
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
element.layout(state, cx)
})
})
}
fn paint(&mut self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
cx.with_element_id(self.entity_id(), |_global_id, cx| {
self.state.update(cx, |state, cx| {
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
element.paint(state, cx);
});
});
}
}
pub struct AnyView {
view: Arc<Mutex<dyn ViewObject>>,
}
impl<ParentV> IntoAnyElement<ParentV> for AnyView
where
ParentV: 'static + Send + Sync,
{
fn into_any(self) -> AnyElement<ParentV> {
AnyElement::new(EraseAnyViewState {
view: self,
parent_view_state_type: PhantomData,
})
}
}
impl Element for AnyView {
type ViewState = ();
type ElementState = AnyBox;
fn id(&self) -> Option<crate::ElementId> {
Some(ElementId::View(self.view.lock().entity_id()))
}
fn initialize(
&mut self,
_: &mut Self::ViewState,
_: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> Self::ElementState {
self.view.lock().initialize(cx)
}
fn layout(
&mut self,
_: &mut Self::ViewState,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId {
self.view.lock().layout(element, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut (),
element: &mut AnyBox,
cx: &mut ViewContext<Self::ViewState>,
) {
self.view.lock().paint(bounds, element, cx)
}
}
struct EraseAnyViewState<ParentViewState> {
view: AnyView,
parent_view_state_type: PhantomData<ParentViewState>,
}
impl<ParentV> IntoAnyElement<ParentV> for EraseAnyViewState<ParentV>
where
ParentV: 'static + Send + Sync,
{
fn into_any(self) -> AnyElement<ParentV> {
AnyElement::new(self)
}
}
impl<ParentV> Element for EraseAnyViewState<ParentV>
where
ParentV: 'static + Send + Sync,
{
type ViewState = ParentV;
type ElementState = AnyBox;
fn id(&self) -> Option<crate::ElementId> {
Element::id(&self.view)
}
fn initialize(
&mut self,
_: &mut Self::ViewState,
_: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> Self::ElementState {
self.view.view.lock().initialize(cx)
}
fn layout(
&mut self,
_: &mut Self::ViewState,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) -> LayoutId {
self.view.view.lock().layout(element, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::ViewState,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) {
self.view.view.lock().paint(bounds, element, cx)
}
}
impl Clone for AnyView {
fn clone(&self) -> Self {
Self {
view: self.view.clone(),
}
}
}
File diff suppressed because it is too large Load Diff