Merge branch 'main' into randomized-tests-operation-script
This commit is contained in:
commit
c960277349
483 changed files with 32884 additions and 10315 deletions
|
@ -36,12 +36,14 @@ parking = "2.0.0"
|
|||
parking_lot = "0.11.1"
|
||||
pathfinder_color = "0.5"
|
||||
pathfinder_geometry = "0.5"
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
postage = { workspace = true }
|
||||
rand = "0.8.3"
|
||||
resvg = "0.14"
|
||||
schemars = "0.8"
|
||||
seahash = "4.1"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
|
|
|
@ -56,7 +56,10 @@ impl gpui::Element for TextElement {
|
|||
cx: &mut gpui::PaintContext,
|
||||
) -> Self::PaintState {
|
||||
let font_size = 12.;
|
||||
let family = cx.font_cache.load_family(&["SF Pro Display"]).unwrap();
|
||||
let family = cx
|
||||
.font_cache
|
||||
.load_family(&["SF Pro Display"], &Default::default())
|
||||
.unwrap();
|
||||
let normal = RunStyle {
|
||||
font_id: cx
|
||||
.font_cache
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,14 @@ pub trait Action: 'static {
|
|||
Self: Sized;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn Action {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("dyn Action")
|
||||
.field("namespace", &self.namespace())
|
||||
.field("name", &self.name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
/// Define a set of unit struct types that all implement the `Action` trait.
|
||||
///
|
||||
/// The first argument is a namespace that will be associated with each of
|
||||
|
|
|
@ -11,9 +11,46 @@ pub enum MenuItem<'a> {
|
|||
Action {
|
||||
name: &'a str,
|
||||
action: Box<dyn Action>,
|
||||
os_action: Option<OsAction>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> MenuItem<'a> {
|
||||
pub fn separator() -> Self {
|
||||
Self::Separator
|
||||
}
|
||||
|
||||
pub fn submenu(menu: Menu<'a>) -> Self {
|
||||
Self::Submenu(menu)
|
||||
}
|
||||
|
||||
pub fn action(name: &'a str, action: impl Action) -> Self {
|
||||
Self::Action {
|
||||
name,
|
||||
action: Box::new(action),
|
||||
os_action: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
|
||||
Self::Action {
|
||||
name,
|
||||
action: Box::new(action),
|
||||
os_action: Some(os_action),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum OsAction {
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
SelectAll,
|
||||
Undo,
|
||||
Redo,
|
||||
}
|
||||
|
||||
impl MutableAppContext {
|
||||
pub fn set_menus(&mut self, menus: Vec<Menu>) {
|
||||
self.foreground_platform
|
||||
|
@ -40,9 +77,9 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
|
|||
let cx = app.0.clone();
|
||||
move |action| {
|
||||
let mut cx = cx.borrow_mut();
|
||||
if let Some(key_window_id) = cx.cx.platform.key_window_id() {
|
||||
if let Some(view_id) = cx.focused_view_id(key_window_id) {
|
||||
cx.handle_dispatch_action_from_effect(key_window_id, Some(view_id), action);
|
||||
if let Some(main_window_id) = cx.cx.platform.main_window_id() {
|
||||
if let Some(view_id) = cx.focused_view_id(main_window_id) {
|
||||
cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,10 @@ use smol::stream::StreamExt;
|
|||
|
||||
use crate::{
|
||||
executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
|
||||
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent,
|
||||
ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith,
|
||||
RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle,
|
||||
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, Handle, InputHandler,
|
||||
KeyDownEvent, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith,
|
||||
ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle,
|
||||
WeakHandle,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
|
||||
|
@ -137,11 +138,7 @@ impl TestAppContext {
|
|||
(window_id, view)
|
||||
}
|
||||
|
||||
pub fn add_view<T, F>(
|
||||
&mut self,
|
||||
parent_handle: impl Into<AnyViewHandle>,
|
||||
build_view: F,
|
||||
) -> ViewHandle<T>
|
||||
pub fn add_view<T, F>(&mut self, parent_handle: &AnyViewHandle, build_view: F) -> ViewHandle<T>
|
||||
where
|
||||
T: View,
|
||||
F: FnOnce(&mut ViewContext<T>) -> T,
|
||||
|
@ -330,6 +327,14 @@ impl TestAppContext {
|
|||
.assert_dropped(handle.id())
|
||||
}
|
||||
|
||||
/// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
|
||||
/// where the stray handles were created.
|
||||
pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
|
||||
let weak = handle.downgrade();
|
||||
self.update(|_| drop(handle));
|
||||
self.assert_dropped(weak);
|
||||
}
|
||||
|
||||
fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
|
||||
std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
|
||||
let (_, window) = state
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use std::{borrow::Cow, cell::RefCell, collections::HashMap};
|
||||
use image::ImageFormat;
|
||||
use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::ImageData;
|
||||
|
||||
pub trait AssetSource: 'static + Send + Sync {
|
||||
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
|
||||
|
@ -22,6 +25,7 @@ impl AssetSource for () {
|
|||
pub struct AssetCache {
|
||||
source: Box<dyn AssetSource>,
|
||||
svgs: RefCell<HashMap<String, usvg::Tree>>,
|
||||
pngs: RefCell<HashMap<String, Arc<ImageData>>>,
|
||||
}
|
||||
|
||||
impl AssetCache {
|
||||
|
@ -29,6 +33,7 @@ impl AssetCache {
|
|||
Self {
|
||||
source: Box::new(source),
|
||||
svgs: RefCell::new(HashMap::new()),
|
||||
pngs: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,4 +48,18 @@ impl AssetCache {
|
|||
Ok(svg)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn png(&self, path: &str) -> Result<Arc<ImageData>> {
|
||||
let mut pngs = self.pngs.borrow_mut();
|
||||
if let Some(png) = pngs.get(path) {
|
||||
Ok(png.clone())
|
||||
} else {
|
||||
let bytes = self.source.load(path)?;
|
||||
let image = ImageData::new(
|
||||
image::load_from_memory_with_format(&bytes, ImageFormat::Png)?.into_bgra8(),
|
||||
);
|
||||
pngs.insert(path.to_string(), image.clone());
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -296,7 +296,10 @@ impl<T: Element> AnyElement for Lifecycle<T> {
|
|||
paint,
|
||||
}
|
||||
}
|
||||
_ => panic!("invalid element lifecycle state"),
|
||||
Lifecycle::Empty => panic!("invalid element lifecycle state"),
|
||||
Lifecycle::Init { .. } => {
|
||||
panic!("invalid element lifecycle state, paint called before layout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,6 +366,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
|
|||
value
|
||||
}
|
||||
}
|
||||
|
||||
_ => panic!("invalid element lifecycle state"),
|
||||
}
|
||||
}
|
||||
|
@ -385,6 +389,12 @@ impl ElementBox {
|
|||
}
|
||||
}
|
||||
|
||||
impl Clone for ElementBox {
|
||||
fn clone(&self) -> Self {
|
||||
ElementBox(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ElementBox> for ElementRc {
|
||||
fn from(val: ElementBox) -> Self {
|
||||
val.0
|
||||
|
|
|
@ -153,7 +153,9 @@ impl Element for ConstrainedBox {
|
|||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
) -> Self::PaintState {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
cx.paint_layer(Some(visible_bounds), |cx| {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
})
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
|
|
@ -22,6 +22,7 @@ pub struct Flex {
|
|||
axis: Axis,
|
||||
children: Vec<ElementBox>,
|
||||
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
|
||||
child_alignment: f32,
|
||||
}
|
||||
|
||||
impl Flex {
|
||||
|
@ -30,6 +31,7 @@ impl Flex {
|
|||
axis,
|
||||
children: Default::default(),
|
||||
scroll_state: None,
|
||||
child_alignment: -1.,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +43,15 @@ impl Flex {
|
|||
Self::new(Axis::Vertical)
|
||||
}
|
||||
|
||||
/// Render children centered relative to the cross-axis of the parent flex.
|
||||
///
|
||||
/// If this is a flex row, children will be centered vertically. If this is a
|
||||
/// flex column, children will be centered horizontally.
|
||||
pub fn align_children_center(mut self) -> Self {
|
||||
self.child_alignment = 0.;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn scrollable<Tag, V>(
|
||||
mut self,
|
||||
element_id: usize,
|
||||
|
@ -308,7 +319,32 @@ impl Element for Flex {
|
|||
}
|
||||
}
|
||||
}
|
||||
child.paint(child_origin, visible_bounds, cx);
|
||||
|
||||
// We use the child_alignment f32 to determine a point along the cross axis of the
|
||||
// overall flex element and each child. We then align these points. So 0 would center
|
||||
// each child relative to the overall height/width of the flex. -1 puts children at
|
||||
// the start. 1 puts children at the end.
|
||||
let aligned_child_origin = {
|
||||
let cross_axis = self.axis.invert();
|
||||
let my_center = bounds.size().along(cross_axis) / 2.;
|
||||
let my_target = my_center + my_center * self.child_alignment;
|
||||
|
||||
let child_center = child.size().along(cross_axis) / 2.;
|
||||
let child_target = child_center + child_center * self.child_alignment;
|
||||
|
||||
let mut aligned_child_origin = child_origin;
|
||||
match self.axis {
|
||||
Axis::Horizontal => aligned_child_origin
|
||||
.set_y(aligned_child_origin.y() - (child_target - my_target)),
|
||||
Axis::Vertical => aligned_child_origin
|
||||
.set_x(aligned_child_origin.x() - (child_target - my_target)),
|
||||
}
|
||||
|
||||
aligned_child_origin
|
||||
};
|
||||
|
||||
child.paint(aligned_child_origin, visible_bounds, cx);
|
||||
|
||||
match self.axis {
|
||||
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
|
||||
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
|
||||
|
|
|
@ -11,8 +11,13 @@ use crate::{
|
|||
use serde::Deserialize;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
enum ImageSource {
|
||||
Path(&'static str),
|
||||
Data(Arc<ImageData>),
|
||||
}
|
||||
|
||||
pub struct Image {
|
||||
data: Arc<ImageData>,
|
||||
source: ImageSource,
|
||||
style: ImageStyle,
|
||||
}
|
||||
|
||||
|
@ -31,9 +36,16 @@ pub struct ImageStyle {
|
|||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(data: Arc<ImageData>) -> Self {
|
||||
pub fn new(asset_path: &'static str) -> Self {
|
||||
Self {
|
||||
data,
|
||||
source: ImageSource::Path(asset_path),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_data(data: Arc<ImageData>) -> Self {
|
||||
Self {
|
||||
source: ImageSource::Data(data),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -45,39 +57,53 @@ impl Image {
|
|||
}
|
||||
|
||||
impl Element for Image {
|
||||
type LayoutState = ();
|
||||
type LayoutState = Option<Arc<ImageData>>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
_: &mut LayoutContext,
|
||||
cx: &mut LayoutContext,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let data = match &self.source {
|
||||
ImageSource::Path(path) => match cx.asset_cache.png(path) {
|
||||
Ok(data) => data,
|
||||
Err(error) => {
|
||||
log::error!("could not load image: {}", error);
|
||||
return (Vector2F::zero(), None);
|
||||
}
|
||||
},
|
||||
ImageSource::Data(data) => data.clone(),
|
||||
};
|
||||
|
||||
let desired_size = vec2f(
|
||||
self.style.width.unwrap_or_else(|| constraint.max.x()),
|
||||
self.style.height.unwrap_or_else(|| constraint.max.y()),
|
||||
);
|
||||
let size = constrain_size_preserving_aspect_ratio(
|
||||
constraint.constrain(desired_size),
|
||||
self.data.size().to_f32(),
|
||||
data.size().to_f32(),
|
||||
);
|
||||
(size, ())
|
||||
|
||||
(size, Some(data))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: RectF,
|
||||
_: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
layout: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
) -> Self::PaintState {
|
||||
cx.scene.push_image(scene::Image {
|
||||
bounds,
|
||||
border: self.style.border,
|
||||
corner_radius: self.style.corner_radius,
|
||||
grayscale: self.style.grayscale,
|
||||
data: self.data.clone(),
|
||||
});
|
||||
if let Some(data) = layout {
|
||||
cx.scene.push_image(scene::Image {
|
||||
bounds,
|
||||
border: self.style.border,
|
||||
corner_radius: self.style.corner_radius,
|
||||
grayscale: self.style.grayscale,
|
||||
data: data.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::ops::Range;
|
||||
use std::{borrow::Cow, ops::Range};
|
||||
|
||||
use crate::{
|
||||
fonts::TextStyle,
|
||||
|
@ -16,7 +16,7 @@ use serde_json::json;
|
|||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
pub struct Label {
|
||||
text: String,
|
||||
text: Cow<'static, str>,
|
||||
style: LabelStyle,
|
||||
highlight_indices: Vec<usize>,
|
||||
}
|
||||
|
@ -44,9 +44,9 @@ impl LabelStyle {
|
|||
}
|
||||
|
||||
impl Label {
|
||||
pub fn new(text: String, style: impl Into<LabelStyle>) -> Self {
|
||||
pub fn new<I: Into<Cow<'static, str>>>(text: I, style: impl Into<LabelStyle>) -> Self {
|
||||
Self {
|
||||
text,
|
||||
text: text.into(),
|
||||
highlight_indices: Default::default(),
|
||||
style: style.into(),
|
||||
}
|
||||
|
@ -138,11 +138,9 @@ impl Element for Label {
|
|||
cx: &mut LayoutContext,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let runs = self.compute_runs();
|
||||
let line = cx.text_layout_cache.layout_str(
|
||||
self.text.as_str(),
|
||||
self.style.text.font_size,
|
||||
runs.as_slice(),
|
||||
);
|
||||
let line =
|
||||
cx.text_layout_cache
|
||||
.layout_str(&self.text, self.style.text.font_size, runs.as_slice());
|
||||
|
||||
let size = vec2f(
|
||||
line.width()
|
||||
|
@ -218,6 +216,7 @@ mod tests {
|
|||
12.,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Color::black(),
|
||||
cx.font_cache(),
|
||||
)
|
||||
|
@ -227,6 +226,7 @@ mod tests {
|
|||
12.,
|
||||
*FontProperties::new().weight(Weight::BOLD),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Color::new(255, 0, 0, 255),
|
||||
cx.font_cache(),
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ use serde_json::json;
|
|||
use std::{borrow::Cow, ops::Range, sync::Arc};
|
||||
|
||||
pub struct Text {
|
||||
text: String,
|
||||
text: Cow<'static, str>,
|
||||
style: TextStyle,
|
||||
soft_wrap: bool,
|
||||
highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
|
@ -28,9 +28,9 @@ pub struct LayoutState {
|
|||
}
|
||||
|
||||
impl Text {
|
||||
pub fn new(text: String, style: TextStyle) -> Self {
|
||||
pub fn new<I: Into<Cow<'static, str>>>(text: I, style: TextStyle) -> Self {
|
||||
Self {
|
||||
text,
|
||||
text: text.into(),
|
||||
style,
|
||||
soft_wrap: true,
|
||||
highlights: Vec::new(),
|
||||
|
@ -280,7 +280,7 @@ mod tests {
|
|||
let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
|
||||
let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
|
||||
fonts::with_font_cache(cx.font_cache().clone(), || {
|
||||
let mut text = Text::new("Hello\r\n".into(), Default::default()).with_soft_wrap(true);
|
||||
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
|
||||
let (_, state) = text.layout(
|
||||
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
|
||||
&mut presenter.build_layout_context(Default::default(), false, cx),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
fonts::{FontId, Metrics, Properties},
|
||||
fonts::{Features, FontId, Metrics, Properties},
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
platform,
|
||||
text_layout::LineWrapper,
|
||||
|
@ -18,6 +18,7 @@ pub struct FamilyId(usize);
|
|||
|
||||
struct Family {
|
||||
name: Arc<str>,
|
||||
font_features: Features,
|
||||
font_ids: Vec<FontId>,
|
||||
}
|
||||
|
||||
|
@ -58,17 +59,21 @@ impl FontCache {
|
|||
.map(|family| family.name.clone())
|
||||
}
|
||||
|
||||
pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
|
||||
pub fn load_family(&self, names: &[&str], features: &Features) -> Result<FamilyId> {
|
||||
for name in names {
|
||||
let state = self.0.upgradable_read();
|
||||
|
||||
if let Some(ix) = state.families.iter().position(|f| f.name.as_ref() == *name) {
|
||||
if let Some(ix) = state
|
||||
.families
|
||||
.iter()
|
||||
.position(|f| f.name.as_ref() == *name && f.font_features == *features)
|
||||
{
|
||||
return Ok(FamilyId(ix));
|
||||
}
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
|
||||
if let Ok(font_ids) = state.fonts.load_family(name) {
|
||||
if let Ok(font_ids) = state.fonts.load_family(name, features) {
|
||||
if font_ids.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
@ -82,6 +87,7 @@ impl FontCache {
|
|||
|
||||
state.families.push(Family {
|
||||
name: Arc::from(*name),
|
||||
font_features: features.clone(),
|
||||
font_ids,
|
||||
});
|
||||
return Ok(family_id);
|
||||
|
@ -254,7 +260,15 @@ mod tests {
|
|||
fn test_select_font() {
|
||||
let platform = test::platform();
|
||||
let fonts = FontCache::new(platform.fonts());
|
||||
let arial = fonts.load_family(&["Arial"]).unwrap();
|
||||
let arial = fonts
|
||||
.load_family(
|
||||
&["Arial"],
|
||||
&Features {
|
||||
calt: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap();
|
||||
let arial_italic = fonts
|
||||
.select_font(arial, Properties::new().style(Style::Italic))
|
||||
|
@ -265,5 +279,16 @@ mod tests {
|
|||
assert_ne!(arial_regular, arial_italic);
|
||||
assert_ne!(arial_regular, arial_bold);
|
||||
assert_ne!(arial_italic, arial_bold);
|
||||
|
||||
let arial_with_calt = fonts
|
||||
.load_family(
|
||||
&["Arial"],
|
||||
&Features {
|
||||
calt: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_ne!(arial_with_calt, arial);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ pub use font_kit::{
|
|||
properties::{Properties, Stretch, Style, Weight},
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde::{de, Deserialize};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
|
||||
|
@ -20,6 +21,44 @@ pub struct FontId(pub usize);
|
|||
|
||||
pub type GlyphId = u32;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Features {
|
||||
pub calt: Option<bool>,
|
||||
pub case: Option<bool>,
|
||||
pub cpsp: Option<bool>,
|
||||
pub frac: Option<bool>,
|
||||
pub liga: Option<bool>,
|
||||
pub onum: Option<bool>,
|
||||
pub ordn: Option<bool>,
|
||||
pub pnum: Option<bool>,
|
||||
pub ss01: Option<bool>,
|
||||
pub ss02: Option<bool>,
|
||||
pub ss03: Option<bool>,
|
||||
pub ss04: Option<bool>,
|
||||
pub ss05: Option<bool>,
|
||||
pub ss06: Option<bool>,
|
||||
pub ss07: Option<bool>,
|
||||
pub ss08: Option<bool>,
|
||||
pub ss09: Option<bool>,
|
||||
pub ss10: Option<bool>,
|
||||
pub ss11: Option<bool>,
|
||||
pub ss12: Option<bool>,
|
||||
pub ss13: Option<bool>,
|
||||
pub ss14: Option<bool>,
|
||||
pub ss15: Option<bool>,
|
||||
pub ss16: Option<bool>,
|
||||
pub ss17: Option<bool>,
|
||||
pub ss18: Option<bool>,
|
||||
pub ss19: Option<bool>,
|
||||
pub ss20: Option<bool>,
|
||||
pub subs: Option<bool>,
|
||||
pub sups: Option<bool>,
|
||||
pub swsh: Option<bool>,
|
||||
pub titl: Option<bool>,
|
||||
pub tnum: Option<bool>,
|
||||
pub zero: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextStyle {
|
||||
pub color: Color,
|
||||
|
@ -71,6 +110,8 @@ thread_local! {
|
|||
struct TextStyleJson {
|
||||
color: Color,
|
||||
family: String,
|
||||
#[serde(default)]
|
||||
features: Features,
|
||||
weight: Option<WeightJson>,
|
||||
size: f32,
|
||||
#[serde(default)]
|
||||
|
@ -107,12 +148,13 @@ impl TextStyle {
|
|||
font_family_name: impl Into<Arc<str>>,
|
||||
font_size: f32,
|
||||
font_properties: Properties,
|
||||
font_features: Features,
|
||||
underline: Underline,
|
||||
color: Color,
|
||||
font_cache: &FontCache,
|
||||
) -> Result<Self> {
|
||||
let font_family_name = font_family_name.into();
|
||||
let font_family_id = font_cache.load_family(&[&font_family_name])?;
|
||||
let font_family_id = font_cache.load_family(&[&font_family_name], &font_features)?;
|
||||
let font_id = font_cache.select_font(font_family_id, &font_properties)?;
|
||||
Ok(Self {
|
||||
color,
|
||||
|
@ -175,6 +217,7 @@ impl TextStyle {
|
|||
json.family,
|
||||
json.size,
|
||||
font_properties,
|
||||
json.features,
|
||||
underline_from_json(json.underline),
|
||||
json.color,
|
||||
font_cache,
|
||||
|
@ -253,7 +296,9 @@ impl Default for TextStyle {
|
|||
.expect("TextStyle::default can only be called within a call to with_font_cache");
|
||||
|
||||
let font_family_name = Arc::from("Courier");
|
||||
let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
|
||||
let font_family_id = font_cache
|
||||
.load_family(&[&font_family_name], &Default::default())
|
||||
.unwrap();
|
||||
let font_id = font_cache
|
||||
.select_font(font_family_id, &Default::default())
|
||||
.unwrap();
|
||||
|
|
|
@ -5,7 +5,7 @@ mod keystroke;
|
|||
|
||||
use std::{any::TypeId, fmt::Debug};
|
||||
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use collections::HashMap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::Action;
|
||||
|
@ -68,8 +68,8 @@ impl KeymapMatcher {
|
|||
/// There exist bindings which are still waiting for more keys.
|
||||
/// MatchResult::Complete(matches) =>
|
||||
/// 1 or more bindings have recieved the necessary key presses.
|
||||
/// The order of the matched actions is by order in the keymap file first and
|
||||
/// position of the matching view second.
|
||||
/// The order of the matched actions is by position of the matching first,
|
||||
// and order in the keymap second.
|
||||
pub fn push_keystroke(
|
||||
&mut self,
|
||||
keystroke: Keystroke,
|
||||
|
@ -80,8 +80,7 @@ impl KeymapMatcher {
|
|||
// and then the order the binding matched in the view tree second.
|
||||
// The key is the reverse position of the binding in the bindings list so that later bindings
|
||||
// match before earlier ones in the user's config
|
||||
let mut matched_bindings: BTreeMap<usize, Vec<(usize, Box<dyn Action>)>> =
|
||||
Default::default();
|
||||
let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
|
||||
|
||||
let first_keystroke = self.pending_keystrokes.is_empty();
|
||||
self.pending_keystrokes.push(keystroke.clone());
|
||||
|
@ -105,14 +104,11 @@ impl KeymapMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
for (order, binding) in self.keymap.bindings().iter().rev().enumerate() {
|
||||
for binding in self.keymap.bindings().iter().rev() {
|
||||
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
|
||||
{
|
||||
BindingMatchResult::Complete(action) => {
|
||||
matched_bindings
|
||||
.entry(order)
|
||||
.or_default()
|
||||
.push((*view_id, action));
|
||||
matched_bindings.push((*view_id, action));
|
||||
}
|
||||
BindingMatchResult::Partial => {
|
||||
self.pending_views
|
||||
|
@ -131,7 +127,7 @@ impl KeymapMatcher {
|
|||
if !matched_bindings.is_empty() {
|
||||
// Collect the sorted matched bindings into the final vec for ease of use
|
||||
// Matched bindings are in order by precedence
|
||||
MatchResult::Matches(matched_bindings.into_values().flatten().collect())
|
||||
MatchResult::Matches(matched_bindings)
|
||||
} else if any_pending {
|
||||
MatchResult::Pending
|
||||
} else {
|
||||
|
@ -225,15 +221,47 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_keymap_and_view_ordering() -> Result<()> {
|
||||
actions!(test, [EditorAction, ProjectPanelAction]);
|
||||
|
||||
let mut editor = KeymapContext::default();
|
||||
editor.add_identifier("Editor");
|
||||
|
||||
let mut project_panel = KeymapContext::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.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
|
||||
MatchResult::Matches(vec![
|
||||
(2, Box::new(EditorAction)),
|
||||
(1, Box::new(ProjectPanelAction)),
|
||||
]),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_keystroke() -> Result<()> {
|
||||
actions!(test, [B, AB, C, D, DA]);
|
||||
actions!(test, [B, AB, C, D, DA, E, EF]);
|
||||
|
||||
let mut context1 = KeymapContext::default();
|
||||
context1.set.insert("1".into());
|
||||
context1.add_identifier("1");
|
||||
|
||||
let mut context2 = KeymapContext::default();
|
||||
context2.set.insert("2".into());
|
||||
context2.add_identifier("2");
|
||||
|
||||
let dispatch_path = vec![(2, context2), (1, context1)];
|
||||
|
||||
|
@ -286,6 +314,7 @@ mod tests {
|
|||
matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
|
||||
MatchResult::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!(
|
||||
|
@ -366,22 +395,22 @@ mod tests {
|
|||
let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
|
||||
|
||||
let mut context = KeymapContext::default();
|
||||
context.set.insert("a".into());
|
||||
context.add_identifier("a");
|
||||
assert!(!predicate.eval(&[context]));
|
||||
|
||||
let mut context = KeymapContext::default();
|
||||
context.set.insert("a".into());
|
||||
context.set.insert("b".into());
|
||||
context.add_identifier("a");
|
||||
context.add_identifier("b");
|
||||
assert!(predicate.eval(&[context]));
|
||||
|
||||
let mut context = KeymapContext::default();
|
||||
context.set.insert("a".into());
|
||||
context.map.insert("c".into(), "x".into());
|
||||
context.add_identifier("a");
|
||||
context.add_key("c", "x");
|
||||
assert!(!predicate.eval(&[context]));
|
||||
|
||||
let mut context = KeymapContext::default();
|
||||
context.set.insert("a".into());
|
||||
context.map.insert("c".into(), "d".into());
|
||||
context.add_identifier("a");
|
||||
context.add_key("c", "d");
|
||||
assert!(predicate.eval(&[context]));
|
||||
|
||||
let predicate = KeymapContextPredicate::parse("!a").unwrap();
|
||||
|
@ -421,10 +450,11 @@ mod tests {
|
|||
assert!(!predicate.eval(&contexts[6..]));
|
||||
|
||||
fn context_set(names: &[&str]) -> KeymapContext {
|
||||
KeymapContext {
|
||||
set: names.iter().copied().map(str::to_string).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
let mut keymap = KeymapContext::new();
|
||||
names
|
||||
.iter()
|
||||
.for_each(|name| keymap.add_identifier(name.to_string()));
|
||||
keymap
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,10 +477,10 @@ mod tests {
|
|||
]);
|
||||
|
||||
let mut context_a = KeymapContext::default();
|
||||
context_a.set.insert("a".into());
|
||||
context_a.add_identifier("a");
|
||||
|
||||
let mut context_b = KeymapContext::default();
|
||||
context_b.set.insert("b".into());
|
||||
context_b.add_identifier("b");
|
||||
|
||||
let mut matcher = KeymapMatcher::new(keymap);
|
||||
|
||||
|
@ -495,7 +525,7 @@ mod tests {
|
|||
matcher.clear_pending();
|
||||
|
||||
let mut context_c = KeymapContext::default();
|
||||
context_c.set.insert("c".into());
|
||||
context_c.add_identifier("c");
|
||||
|
||||
// Pending keystrokes are maintained per-view
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct KeymapContext {
|
||||
pub set: HashSet<String>,
|
||||
pub map: HashMap<String, String>,
|
||||
set: HashSet<Cow<'static, str>>,
|
||||
map: HashMap<Cow<'static, str>, Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl KeymapContext {
|
||||
pub fn new() -> Self {
|
||||
KeymapContext {
|
||||
set: HashSet::default(),
|
||||
map: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: &Self) {
|
||||
for v in &other.set {
|
||||
self.set.insert(v.clone());
|
||||
|
@ -16,6 +25,18 @@ impl KeymapContext {
|
|||
self.map.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
|
||||
self.set.insert(identifier.into());
|
||||
}
|
||||
|
||||
pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
|
||||
&mut self,
|
||||
key: S1,
|
||||
value: S2,
|
||||
) {
|
||||
self.map.insert(key.into(), value.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
|
@ -46,12 +67,12 @@ impl KeymapContextPredicate {
|
|||
Self::Identifier(name) => (&context.set).contains(name.as_str()),
|
||||
Self::Equal(left, right) => context
|
||||
.map
|
||||
.get(left)
|
||||
.get(left.as_str())
|
||||
.map(|value| value == right)
|
||||
.unwrap_or(false),
|
||||
Self::NotEqual(left, right) => context
|
||||
.map
|
||||
.get(left)
|
||||
.get(left.as_str())
|
||||
.map(|value| value != right)
|
||||
.unwrap_or(true),
|
||||
Self::Not(pred) => !pred.eval(contexts),
|
||||
|
|
|
@ -9,7 +9,10 @@ pub mod current {
|
|||
|
||||
use crate::{
|
||||
executor,
|
||||
fonts::{FontId, GlyphId, Metrics as FontMetrics, Properties as FontProperties},
|
||||
fonts::{
|
||||
Features as FontFeatures, FontId, GlyphId, Metrics as FontMetrics,
|
||||
Properties as FontProperties,
|
||||
},
|
||||
geometry::{
|
||||
rect::{RectF, RectI},
|
||||
vector::Vector2F,
|
||||
|
@ -58,7 +61,7 @@ pub trait Platform: Send + Sync {
|
|||
options: WindowOptions,
|
||||
executor: Rc<executor::Foreground>,
|
||||
) -> Box<dyn Window>;
|
||||
fn key_window_id(&self) -> Option<usize>;
|
||||
fn main_window_id(&self) -> Option<usize>;
|
||||
|
||||
fn add_status_item(&self) -> Box<dyn Window>;
|
||||
|
||||
|
@ -87,6 +90,10 @@ pub(crate) trait ForegroundPlatform {
|
|||
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()>);
|
||||
|
||||
/// Handle the application being re-activated with no windows open.
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
||||
|
||||
fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
|
||||
|
@ -335,7 +342,7 @@ pub enum RasterizationOptions {
|
|||
|
||||
pub trait FontSystem: Send + Sync {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
|
||||
fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>>;
|
||||
fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
|
||||
fn select_font(
|
||||
&self,
|
||||
font_ids: &[FontId],
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod open_type;
|
||||
|
||||
use crate::{
|
||||
fonts::{FontId, GlyphId, Metrics, Properties},
|
||||
fonts::{Features, FontId, GlyphId, Metrics, Properties},
|
||||
geometry::{
|
||||
rect::{RectF, RectI},
|
||||
transform2d::Transform2F,
|
||||
|
@ -64,8 +66,8 @@ impl platform::FontSystem for FontSystem {
|
|||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>> {
|
||||
self.0.write().load_family(name)
|
||||
fn load_family(&self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
|
||||
self.0.write().load_family(name, features)
|
||||
}
|
||||
|
||||
fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
|
||||
|
@ -126,7 +128,7 @@ impl FontSystemState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn load_family(&mut self, name: &str) -> anyhow::Result<Vec<FontId>> {
|
||||
fn load_family(&mut self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
|
||||
let mut font_ids = Vec::new();
|
||||
|
||||
let family = self
|
||||
|
@ -134,7 +136,8 @@ impl FontSystemState {
|
|||
.select_family_by_name(name)
|
||||
.or_else(|_| self.system_source.select_family_by_name(name))?;
|
||||
for font in family.fonts() {
|
||||
let font = font.load()?;
|
||||
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();
|
||||
|
@ -503,7 +506,7 @@ mod tests {
|
|||
fn test_layout_str(_: &mut MutableAppContext) {
|
||||
// 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").unwrap();
|
||||
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(),
|
||||
|
@ -544,13 +547,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_glyph_offsets() -> anyhow::Result<()> {
|
||||
let fonts = FontSystem::new();
|
||||
let zapfino = fonts.load_family("Zapfino")?;
|
||||
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")?;
|
||||
let menlo = fonts.load_family("Menlo", &Default::default())?;
|
||||
let menlo_regular = RunStyle {
|
||||
font_id: fonts.select_font(&menlo, &Properties::new())?,
|
||||
color: Default::default(),
|
||||
|
@ -584,7 +587,7 @@ mod tests {
|
|||
use std::{fs::File, io::BufWriter, path::Path};
|
||||
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Fira Code").unwrap();
|
||||
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();
|
||||
|
||||
|
@ -618,7 +621,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_wrap_line() {
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Helvetica").unwrap();
|
||||
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";
|
||||
|
@ -636,7 +639,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_layout_line_bom_char() {
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Helvetica").unwrap();
|
||||
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(),
|
||||
|
|
395
crates/gpui/src/platform/mac/fonts/open_type.rs
Normal file
395
crates/gpui/src/platform/mac/fonts/open_type.rs
Normal file
|
@ -0,0 +1,395 @@
|
|||
#![allow(unused, non_upper_case_globals)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use crate::fonts::Features;
|
||||
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;
|
||||
|
||||
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: &Features) {
|
||||
// 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;
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use cocoa::{
|
||||
appkit::NSWindow,
|
||||
base::id,
|
||||
foundation::{NSPoint, NSRect, NSSize},
|
||||
foundation::{NSPoint, NSRect},
|
||||
};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use pathfinder_geometry::{
|
||||
|
@ -25,61 +24,15 @@ impl Vector2FExt for Vector2F {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait RectFExt {
|
||||
/// Converts self to an NSRect with y axis pointing up.
|
||||
/// The resulting NSRect will have an origin at the bottom left of the rectangle.
|
||||
/// Also takes care of converting from window scaled coordinates to screen coordinates
|
||||
fn to_screen_ns_rect(&self, native_window: id) -> NSRect;
|
||||
|
||||
/// Converts self to an NSRect with y axis point up.
|
||||
/// The resulting NSRect will have an origin at the bottom left of the rectangle.
|
||||
/// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale
|
||||
fn to_ns_rect(&self) -> NSRect;
|
||||
}
|
||||
impl RectFExt for RectF {
|
||||
fn to_screen_ns_rect(&self, native_window: id) -> NSRect {
|
||||
unsafe { native_window.convertRectToScreen_(self.to_ns_rect()) }
|
||||
}
|
||||
|
||||
fn to_ns_rect(&self) -> NSRect {
|
||||
NSRect::new(
|
||||
NSPoint::new(
|
||||
self.origin_x() as f64,
|
||||
-(self.origin_y() + self.height()) as f64,
|
||||
),
|
||||
NSSize::new(self.width() as f64, self.height() as f64),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NSRectExt {
|
||||
/// Converts self to a RectF with y axis pointing down.
|
||||
/// The resulting RectF will have an origin at the top left of the rectangle.
|
||||
/// Also takes care of converting from screen scale coordinates to window coordinates
|
||||
fn to_window_rectf(&self, native_window: id) -> RectF;
|
||||
|
||||
/// Converts self to a RectF with y axis pointing down.
|
||||
/// The resulting RectF will have an origin at the top left of the rectangle.
|
||||
/// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale
|
||||
fn to_rectf(&self) -> RectF;
|
||||
|
||||
fn intersects(&self, other: Self) -> bool;
|
||||
}
|
||||
impl NSRectExt for NSRect {
|
||||
fn to_window_rectf(&self, native_window: id) -> RectF {
|
||||
unsafe {
|
||||
self.origin.x;
|
||||
let rect: NSRect = native_window.convertRectFromScreen_(*self);
|
||||
rect.to_rectf()
|
||||
}
|
||||
}
|
||||
|
||||
impl NSRectExt for NSRect {
|
||||
fn to_rectf(&self) -> RectF {
|
||||
RectF::new(
|
||||
vec2f(
|
||||
self.origin.x as f32,
|
||||
-(self.origin.y + self.size.height) as f32,
|
||||
),
|
||||
vec2f(self.origin.x as f32, self.origin.y as f32),
|
||||
vec2f(self.size.width as f32, self.size.height as f32),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -82,6 +82,10 @@ unsafe fn build_classes() {
|
|||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationShouldHandleReopen:hasVisibleWindows:),
|
||||
should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern "C" fn(&mut Object, Sel, id),
|
||||
|
@ -98,6 +102,31 @@ unsafe fn build_classes() {
|
|||
sel!(handleGPUIMenuItem:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
// Add menu item handlers so that OS save panels have the correct key commands
|
||||
decl.add_method(
|
||||
sel!(cut:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(copy:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(paste:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(selectAll:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(undo:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(redo:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(validateMenuItem:),
|
||||
validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
|
||||
|
@ -119,6 +148,7 @@ pub struct MacForegroundPlatform(RefCell<MacForegroundPlatformState>);
|
|||
pub struct MacForegroundPlatformState {
|
||||
become_active: Option<Box<dyn FnMut()>>,
|
||||
resign_active: Option<Box<dyn FnMut()>>,
|
||||
reopen: Option<Box<dyn FnMut()>>,
|
||||
quit: Option<Box<dyn FnMut()>>,
|
||||
event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
|
||||
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
|
@ -133,15 +163,16 @@ pub struct MacForegroundPlatformState {
|
|||
impl MacForegroundPlatform {
|
||||
pub fn new(foreground: Rc<executor::Foreground>) -> Self {
|
||||
Self(RefCell::new(MacForegroundPlatformState {
|
||||
become_active: Default::default(),
|
||||
resign_active: Default::default(),
|
||||
quit: Default::default(),
|
||||
event: Default::default(),
|
||||
menu_command: Default::default(),
|
||||
validate_menu_command: Default::default(),
|
||||
will_open_menu: Default::default(),
|
||||
open_urls: Default::default(),
|
||||
finish_launching: Default::default(),
|
||||
become_active: None,
|
||||
resign_active: None,
|
||||
reopen: None,
|
||||
quit: None,
|
||||
event: None,
|
||||
menu_command: None,
|
||||
validate_menu_command: None,
|
||||
will_open_menu: None,
|
||||
open_urls: None,
|
||||
finish_launching: None,
|
||||
menu_actions: Default::default(),
|
||||
foreground,
|
||||
}))
|
||||
|
@ -193,11 +224,25 @@ impl MacForegroundPlatform {
|
|||
) -> id {
|
||||
match item {
|
||||
MenuItem::Separator => NSMenuItem::separatorItem(nil),
|
||||
MenuItem::Action { name, action } => {
|
||||
MenuItem::Action {
|
||||
name,
|
||||
action,
|
||||
os_action,
|
||||
} => {
|
||||
// TODO
|
||||
let keystrokes = keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.find(|binding| binding.action().eq(action.as_ref()))
|
||||
.map(|binding| binding.keystrokes());
|
||||
let selector = match os_action {
|
||||
Some(crate::OsAction::Cut) => selector("cut:"),
|
||||
Some(crate::OsAction::Copy) => selector("copy:"),
|
||||
Some(crate::OsAction::Paste) => selector("paste:"),
|
||||
Some(crate::OsAction::SelectAll) => selector("selectAll:"),
|
||||
Some(crate::OsAction::Undo) => selector("undo:"),
|
||||
Some(crate::OsAction::Redo) => selector("redo:"),
|
||||
None => selector("handleGPUIMenuItem:"),
|
||||
};
|
||||
|
||||
let item;
|
||||
if let Some(keystrokes) = keystrokes {
|
||||
|
@ -218,7 +263,7 @@ impl MacForegroundPlatform {
|
|||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
selector,
|
||||
ns_string(key_to_native(&keystroke.key).as_ref()),
|
||||
)
|
||||
.autorelease();
|
||||
|
@ -240,7 +285,7 @@ impl MacForegroundPlatform {
|
|||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(&name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
selector,
|
||||
ns_string(""),
|
||||
)
|
||||
.autorelease();
|
||||
|
@ -249,7 +294,7 @@ impl MacForegroundPlatform {
|
|||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
selector,
|
||||
ns_string(""),
|
||||
)
|
||||
.autorelease();
|
||||
|
@ -293,6 +338,10 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
|
|||
self.0.borrow_mut().quit = Some(callback);
|
||||
}
|
||||
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.borrow_mut().reopen = Some(callback);
|
||||
}
|
||||
|
||||
fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
|
||||
self.0.borrow_mut().event = Some(callback);
|
||||
}
|
||||
|
@ -548,8 +597,8 @@ impl platform::Platform for MacPlatform {
|
|||
Box::new(Window::open(id, options, executor, self.fonts()))
|
||||
}
|
||||
|
||||
fn key_window_id(&self) -> Option<usize> {
|
||||
Window::key_window_id()
|
||||
fn main_window_id(&self) -> Option<usize> {
|
||||
Window::main_window_id()
|
||||
}
|
||||
|
||||
fn add_status_item(&self) -> Box<dyn platform::Window> {
|
||||
|
@ -828,17 +877,37 @@ impl platform::Platform for MacPlatform {
|
|||
}
|
||||
|
||||
fn restart(&self) {
|
||||
#[cfg(debug_assertions)]
|
||||
let path = std::env::current_exe();
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let path = self.app_path().or_else(|_| std::env::current_exe());
|
||||
let app_pid = std::process::id().to_string();
|
||||
let app_path = self
|
||||
.app_path()
|
||||
.ok()
|
||||
// When the app is not bundled, `app_path` returns the
|
||||
// directory containing the executable. Disregard this
|
||||
// and get the path to the executable itself.
|
||||
.and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
|
||||
.unwrap_or_else(|| std::env::current_exe().unwrap());
|
||||
|
||||
let command = path.and_then(|path| Command::new("/usr/bin/open").arg(path).spawn());
|
||||
// Wait until this process has exited and then re-open this path.
|
||||
let script = r#"
|
||||
while kill -0 $0 2> /dev/null; do
|
||||
sleep 0.1
|
||||
done
|
||||
open "$1"
|
||||
"#;
|
||||
|
||||
match command {
|
||||
Err(err) => log::error!("Unable to restart application {}", err),
|
||||
Ok(_child) => self.quit(),
|
||||
let restart_process = Command::new("/bin/bash")
|
||||
.arg("-c")
|
||||
.arg(script)
|
||||
.arg(app_pid)
|
||||
.arg(app_path)
|
||||
.process_group(0)
|
||||
.spawn();
|
||||
|
||||
match restart_process {
|
||||
Ok(_) => self.quit(),
|
||||
Err(e) => log::error!("failed to spawn restart script: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -884,6 +953,15 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
|
|||
}
|
||||
}
|
||||
|
||||
extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
|
||||
if !has_open_windows {
|
||||
let platform = unsafe { get_foreground_platform(this) };
|
||||
if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
|
||||
let platform = unsafe { get_foreground_platform(this) };
|
||||
if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
|
||||
|
|
|
@ -85,16 +85,12 @@ impl SpriteCache {
|
|||
) -> Option<GlyphSprite> {
|
||||
const SUBPIXEL_VARIANTS: u8 = 4;
|
||||
|
||||
let scale_factor = self.scale_factor;
|
||||
let target_position = target_position * scale_factor;
|
||||
let fonts = &self.fonts;
|
||||
let atlases = &mut self.atlases;
|
||||
let target_position = target_position * self.scale_factor;
|
||||
let subpixel_variant = (
|
||||
(target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
|
||||
% SUBPIXEL_VARIANTS,
|
||||
(target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
|
||||
% SUBPIXEL_VARIANTS,
|
||||
(target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
||||
(target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
||||
);
|
||||
|
||||
self.glyphs
|
||||
.entry(GlyphDescriptor {
|
||||
font_id,
|
||||
|
@ -107,16 +103,17 @@ impl SpriteCache {
|
|||
subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
|
||||
subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
|
||||
);
|
||||
let (glyph_bounds, mask) = fonts.rasterize_glyph(
|
||||
let (glyph_bounds, mask) = self.fonts.rasterize_glyph(
|
||||
font_id,
|
||||
font_size,
|
||||
glyph_id,
|
||||
subpixel_shift,
|
||||
scale_factor,
|
||||
self.scale_factor,
|
||||
RasterizationOptions::Alpha,
|
||||
)?;
|
||||
|
||||
let (alloc_id, atlas_bounds) = atlases
|
||||
let (alloc_id, atlas_bounds) = self
|
||||
.atlases
|
||||
.upload(glyph_bounds.size(), &mask)
|
||||
.expect("could not upload glyph");
|
||||
Some(GlyphSprite {
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
mac::platform::NSViewLayerContentsRedrawDuringViewResize,
|
||||
platform::{
|
||||
self,
|
||||
mac::{geometry::RectFExt, renderer::Renderer, screen::Screen},
|
||||
mac::{renderer::Renderer, screen::Screen},
|
||||
Event, WindowBounds,
|
||||
},
|
||||
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
|
||||
|
@ -372,7 +372,8 @@ impl WindowState {
|
|||
}
|
||||
|
||||
let window_frame = self.frame();
|
||||
if window_frame == self.native_window.screen().visibleFrame().to_rectf() {
|
||||
let screen_frame = self.native_window.screen().visibleFrame().to_rectf();
|
||||
if window_frame.size() == screen_frame.size() {
|
||||
WindowBounds::Maximized
|
||||
} else {
|
||||
WindowBounds::Fixed(window_frame)
|
||||
|
@ -383,8 +384,19 @@ impl WindowState {
|
|||
// Returns the window bounds in window coordinates
|
||||
fn frame(&self) -> RectF {
|
||||
unsafe {
|
||||
let ns_frame = NSWindow::frame(self.native_window);
|
||||
ns_frame.to_rectf()
|
||||
let screen_frame = self.native_window.screen().visibleFrame();
|
||||
let window_frame = NSWindow::frame(self.native_window);
|
||||
RectF::new(
|
||||
vec2f(
|
||||
window_frame.origin.x as f32,
|
||||
(screen_frame.size.height - window_frame.origin.y - window_frame.size.height)
|
||||
as f32,
|
||||
),
|
||||
vec2f(
|
||||
window_frame.size.width as f32,
|
||||
window_frame.size.height as f32,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,7 +484,16 @@ impl Window {
|
|||
}
|
||||
WindowBounds::Fixed(rect) => {
|
||||
let screen_frame = screen.visibleFrame();
|
||||
let ns_rect = rect.to_ns_rect();
|
||||
let ns_rect = NSRect::new(
|
||||
NSPoint::new(
|
||||
rect.origin_x() as f64,
|
||||
screen_frame.size.height
|
||||
- rect.origin_y() as f64
|
||||
- rect.height() as f64,
|
||||
),
|
||||
NSSize::new(rect.width() as f64, rect.height() as f64),
|
||||
);
|
||||
|
||||
if ns_rect.intersects(screen_frame) {
|
||||
native_window.setFrame_display_(ns_rect, YES);
|
||||
} else {
|
||||
|
@ -604,12 +625,12 @@ impl Window {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn key_window_id() -> Option<usize> {
|
||||
pub fn main_window_id() -> Option<usize> {
|
||||
unsafe {
|
||||
let app = NSApplication::sharedApplication(nil);
|
||||
let key_window: id = msg_send![app, keyWindow];
|
||||
if msg_send![key_window, isKindOfClass: WINDOW_CLASS] {
|
||||
let id = get_window_state(&*key_window).borrow().id;
|
||||
let main_window: id = msg_send![app, mainWindow];
|
||||
if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
|
||||
let id = get_window_state(&*main_window).borrow().id;
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
|
@ -737,6 +758,7 @@ impl platform::Window for Window {
|
|||
let title = ns_string(title);
|
||||
let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
|
||||
let _: () = msg_send![window, setTitle: title];
|
||||
self.0.borrow().move_traffic_light();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,13 +61,10 @@ impl ForegroundPlatform {
|
|||
|
||||
impl super::ForegroundPlatform for ForegroundPlatform {
|
||||
fn on_become_active(&self, _: Box<dyn FnMut()>) {}
|
||||
|
||||
fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
|
||||
|
||||
fn on_quit(&self, _: Box<dyn FnMut()>) {}
|
||||
|
||||
fn on_reopen(&self, _: Box<dyn FnMut()>) {}
|
||||
fn on_event(&self, _: Box<dyn FnMut(crate::Event) -> bool>) {}
|
||||
|
||||
fn on_open_urls(&self, _: Box<dyn FnMut(Vec<String>)>) {}
|
||||
|
||||
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
|
||||
|
@ -157,7 +154,7 @@ impl super::Platform for Platform {
|
|||
}))
|
||||
}
|
||||
|
||||
fn key_window_id(&self) -> Option<usize> {
|
||||
fn main_window_id(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,9 @@ use crate::{
|
|||
text_layout::TextLayoutCache,
|
||||
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance,
|
||||
AssetCache, ElementBox, Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent,
|
||||
MouseRegion, MouseRegionId, ParentId, ReadModel, ReadView, RenderContext, RenderParams,
|
||||
SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle,
|
||||
WeakViewHandle,
|
||||
MouseRegion, MouseRegionId, MouseState, ParentId, ReadModel, ReadView, RenderContext,
|
||||
RenderParams, SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle,
|
||||
WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
use anyhow::bail;
|
||||
use collections::{HashMap, HashSet};
|
||||
|
@ -507,15 +507,18 @@ impl Presenter {
|
|||
}
|
||||
// Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
|
||||
// not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
|
||||
// This behavior can be overridden by adding a Down handler that calls cx.propogate_event
|
||||
// This behavior can be overridden by adding a Down handler
|
||||
if let MouseEvent::Down(e) = &mouse_event {
|
||||
if valid_region
|
||||
let has_click = valid_region
|
||||
.handlers
|
||||
.contains(MouseEvent::click_disc(), Some(e.button))
|
||||
|| valid_region
|
||||
.handlers
|
||||
.contains(MouseEvent::drag_disc(), Some(e.button))
|
||||
{
|
||||
.contains(MouseEvent::click_disc(), Some(e.button));
|
||||
let has_drag = valid_region
|
||||
.handlers
|
||||
.contains(MouseEvent::drag_disc(), Some(e.button));
|
||||
let has_down = valid_region
|
||||
.handlers
|
||||
.contains(MouseEvent::down_disc(), Some(e.button));
|
||||
if !has_down && (has_click || has_drag) {
|
||||
event_cx.handled = true;
|
||||
}
|
||||
}
|
||||
|
@ -523,14 +526,13 @@ impl Presenter {
|
|||
// `event_consumed` should only be true if there are any handlers for this event.
|
||||
let mut event_consumed = event_cx.handled;
|
||||
if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
|
||||
event_consumed = true;
|
||||
for callback in callbacks {
|
||||
event_cx.handled = true;
|
||||
event_cx.with_current_view(valid_region.id().view_id(), {
|
||||
let region_event = mouse_event.clone();
|
||||
|cx| callback(region_event, cx)
|
||||
});
|
||||
event_consumed &= event_cx.handled;
|
||||
event_consumed |= event_cx.handled;
|
||||
any_event_handled |= event_cx.handled;
|
||||
}
|
||||
}
|
||||
|
@ -603,6 +605,24 @@ pub struct LayoutContext<'a> {
|
|||
}
|
||||
|
||||
impl<'a> LayoutContext<'a> {
|
||||
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
|
||||
let view_id = self.view_stack.last().unwrap();
|
||||
|
||||
let region_id = MouseRegionId::new::<Tag>(*view_id, region_id);
|
||||
MouseState {
|
||||
hovered: self.hovered_region_ids.contains(®ion_id),
|
||||
clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
|
||||
if ids.contains(®ion_id) {
|
||||
Some(*button)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
accessed_hovered: false,
|
||||
accessed_clicked: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
|
||||
let print_error = |view_id| {
|
||||
format!(
|
||||
|
@ -618,7 +638,7 @@ impl<'a> LayoutContext<'a> {
|
|||
(Some(layout_parent), Some(ParentId::View(app_parent))) => {
|
||||
if layout_parent != app_parent {
|
||||
panic!(
|
||||
"View {} was laid out with parent {} when it was constructed with parent {}",
|
||||
"View {} was laid out with parent {} when it was constructed with parent {}",
|
||||
print_error(view_id),
|
||||
print_error(*layout_parent),
|
||||
print_error(*app_parent))
|
||||
|
@ -1039,8 +1059,7 @@ pub struct ChildView {
|
|||
}
|
||||
|
||||
impl ChildView {
|
||||
pub fn new(view: impl Into<AnyViewHandle>, cx: &AppContext) -> Self {
|
||||
let view = view.into();
|
||||
pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
|
||||
let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
|
||||
Self {
|
||||
view: view.downgrade(),
|
||||
|
|
|
@ -663,7 +663,9 @@ mod tests {
|
|||
fn test_wrap_line(cx: &mut crate::MutableAppContext) {
|
||||
let font_cache = cx.font_cache().clone();
|
||||
let font_system = cx.platform().fonts();
|
||||
let family = font_cache.load_family(&["Courier"]).unwrap();
|
||||
let family = font_cache
|
||||
.load_family(&["Courier"], &Default::default())
|
||||
.unwrap();
|
||||
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
|
||||
|
||||
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
|
||||
|
@ -725,7 +727,9 @@ mod tests {
|
|||
let font_system = cx.platform().fonts();
|
||||
let text_layout_cache = TextLayoutCache::new(font_system.clone());
|
||||
|
||||
let family = font_cache.load_family(&["Helvetica"]).unwrap();
|
||||
let family = font_cache
|
||||
.load_family(&["Helvetica"], &Default::default())
|
||||
.unwrap();
|
||||
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
|
||||
let normal = RunStyle {
|
||||
font_id,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue