Checkpoint: emojis rendering
This commit is contained in:
parent
cd1c137542
commit
5c750b6880
12 changed files with 453 additions and 427 deletions
|
@ -48,10 +48,11 @@ fn generate_shader_bindings() -> PathBuf {
|
||||||
"ScaledContentMask".into(),
|
"ScaledContentMask".into(),
|
||||||
"Uniforms".into(),
|
"Uniforms".into(),
|
||||||
"AtlasTile".into(),
|
"AtlasTile".into(),
|
||||||
"Quad".into(),
|
|
||||||
"QuadInputIndex".into(),
|
"QuadInputIndex".into(),
|
||||||
|
"Quad".into(),
|
||||||
|
"SpriteInputIndex".into(),
|
||||||
"MonochromeSprite".into(),
|
"MonochromeSprite".into(),
|
||||||
"MonochromeSpriteInputIndex".into(),
|
"PolychromeSprite".into(),
|
||||||
]);
|
]);
|
||||||
config.no_includes = true;
|
config.no_includes = true;
|
||||||
config.enumeration.prefix_with_name = true;
|
config.enumeration.prefix_with_name = true;
|
||||||
|
|
|
@ -147,8 +147,7 @@ pub trait PlatformWindow {
|
||||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||||
fn draw(&self, scene: Scene);
|
fn draw(&self, scene: Scene);
|
||||||
|
|
||||||
fn monochrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||||
fn polychrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PlatformDispatcher: Send + Sync {
|
pub trait PlatformDispatcher: Send + Sync {
|
||||||
|
@ -182,6 +181,15 @@ pub enum AtlasKey {
|
||||||
Svg(RenderSvgParams),
|
Svg(RenderSvgParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AtlasKey {
|
||||||
|
pub fn is_monochrome(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AtlasKey::Glyph(params) => !params.is_emoji,
|
||||||
|
AtlasKey::Svg(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<RenderGlyphParams> for AtlasKey {
|
impl From<RenderGlyphParams> for AtlasKey {
|
||||||
fn from(params: RenderGlyphParams) -> Self {
|
fn from(params: RenderGlyphParams) -> Self {
|
||||||
Self::Glyph(params)
|
Self::Glyph(params)
|
||||||
|
@ -250,12 +258,6 @@ pub trait PlatformInputHandler {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct ScreenId(pub(crate) Uuid);
|
pub struct ScreenId(pub(crate) Uuid);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum RasterizationOptions {
|
|
||||||
Alpha,
|
|
||||||
Bgra,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
pub bounds: WindowBounds,
|
pub bounds: WindowBounds,
|
||||||
|
|
|
@ -5,26 +5,15 @@ use anyhow::{anyhow, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use etagere::BucketedAtlasAllocator;
|
use etagere::BucketedAtlasAllocator;
|
||||||
use foreign_types::ForeignType;
|
use metal::Device;
|
||||||
use metal::{Device, TextureDescriptor};
|
|
||||||
use objc::{msg_send, sel, sel_impl};
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||||
|
|
||||||
impl MetalAtlas {
|
impl MetalAtlas {
|
||||||
pub fn new(
|
pub fn new(device: Device) -> Self {
|
||||||
size: Size<DevicePixels>,
|
|
||||||
pixel_format: metal::MTLPixelFormat,
|
|
||||||
device: Device,
|
|
||||||
) -> Self {
|
|
||||||
let texture_descriptor = metal::TextureDescriptor::new();
|
|
||||||
texture_descriptor.set_pixel_format(pixel_format);
|
|
||||||
texture_descriptor.set_width(size.width.into());
|
|
||||||
texture_descriptor.set_height(size.height.into());
|
|
||||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||||
device: AssertSend(device),
|
device: AssertSend(device),
|
||||||
texture_descriptor: AssertSend(texture_descriptor),
|
|
||||||
textures: Default::default(),
|
textures: Default::default(),
|
||||||
tiles_by_key: Default::default(),
|
tiles_by_key: Default::default(),
|
||||||
}))
|
}))
|
||||||
|
@ -37,7 +26,6 @@ impl MetalAtlas {
|
||||||
|
|
||||||
struct MetalAtlasState {
|
struct MetalAtlasState {
|
||||||
device: AssertSend<Device>,
|
device: AssertSend<Device>,
|
||||||
texture_descriptor: AssertSend<TextureDescriptor>,
|
|
||||||
textures: Vec<MetalAtlasTexture>,
|
textures: Vec<MetalAtlasTexture>,
|
||||||
tiles_by_key: HashMap<AtlasKey, AtlasTile>,
|
tiles_by_key: HashMap<AtlasKey, AtlasTile>,
|
||||||
}
|
}
|
||||||
|
@ -57,9 +45,15 @@ impl PlatformAtlas for MetalAtlas {
|
||||||
.textures
|
.textures
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
.find_map(|texture| texture.upload(size, &bytes))
|
.find_map(|texture| {
|
||||||
|
if texture.monochrome == key.is_monochrome() {
|
||||||
|
texture.upload(size, &bytes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
let texture = lock.push_texture(size);
|
let texture = lock.push_texture(size, key.is_monochrome());
|
||||||
texture.upload(size, &bytes)
|
texture.upload(size, &bytes)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("could not allocate in new texture"))?;
|
.ok_or_else(|| anyhow!("could not allocate in new texture"))?;
|
||||||
|
@ -74,35 +68,32 @@ impl PlatformAtlas for MetalAtlas {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetalAtlasState {
|
impl MetalAtlasState {
|
||||||
fn push_texture(&mut self, min_size: Size<DevicePixels>) -> &mut MetalAtlasTexture {
|
fn push_texture(
|
||||||
let default_atlas_size = Size {
|
&mut self,
|
||||||
width: self.texture_descriptor.width().into(),
|
min_size: Size<DevicePixels>,
|
||||||
height: self.texture_descriptor.height().into(),
|
monochrome: bool,
|
||||||
|
) -> &mut MetalAtlasTexture {
|
||||||
|
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||||
|
width: DevicePixels(1024),
|
||||||
|
height: DevicePixels(1024),
|
||||||
};
|
};
|
||||||
let size;
|
|
||||||
let metal_texture;
|
|
||||||
|
|
||||||
if min_size.width > default_atlas_size.width || min_size.height > default_atlas_size.height
|
let size = min_size.max(&DEFAULT_ATLAS_SIZE);
|
||||||
{
|
let texture_descriptor = metal::TextureDescriptor::new();
|
||||||
let descriptor = unsafe {
|
texture_descriptor.set_width(size.width.into());
|
||||||
let descriptor_ptr: *mut metal::MTLTextureDescriptor =
|
texture_descriptor.set_height(size.height.into());
|
||||||
msg_send![*self.texture_descriptor, copy];
|
if monochrome {
|
||||||
metal::TextureDescriptor::from_ptr(descriptor_ptr)
|
texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm);
|
||||||
};
|
|
||||||
descriptor.set_width(min_size.width.into());
|
|
||||||
descriptor.set_height(min_size.height.into());
|
|
||||||
descriptor.set_pixel_format(metal::MTLPixelFormat::Depth32Float);
|
|
||||||
size = min_size;
|
|
||||||
metal_texture = self.device.new_texture(&descriptor);
|
|
||||||
} else {
|
} else {
|
||||||
size = default_atlas_size;
|
texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
|
||||||
metal_texture = self.device.new_texture(&self.texture_descriptor);
|
|
||||||
}
|
}
|
||||||
|
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||||
|
|
||||||
let atlas_texture = MetalAtlasTexture {
|
let atlas_texture = MetalAtlasTexture {
|
||||||
id: AtlasTextureId(self.textures.len() as u32),
|
id: AtlasTextureId(self.textures.len() as u32),
|
||||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||||
metal_texture: AssertSend(metal_texture),
|
metal_texture: AssertSend(metal_texture),
|
||||||
|
monochrome,
|
||||||
};
|
};
|
||||||
self.textures.push(atlas_texture);
|
self.textures.push(atlas_texture);
|
||||||
self.textures.last_mut().unwrap()
|
self.textures.last_mut().unwrap()
|
||||||
|
@ -113,6 +104,7 @@ struct MetalAtlasTexture {
|
||||||
id: AtlasTextureId,
|
id: AtlasTextureId,
|
||||||
allocator: BucketedAtlasAllocator,
|
allocator: BucketedAtlasAllocator,
|
||||||
metal_texture: AssertSend<metal::Texture>,
|
metal_texture: AssertSend<metal::Texture>,
|
||||||
|
monochrome: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetalAtlasTexture {
|
impl MetalAtlasTexture {
|
||||||
|
|
|
@ -18,11 +18,11 @@ pub struct MetalRenderer {
|
||||||
layer: metal::MetalLayer,
|
layer: metal::MetalLayer,
|
||||||
command_queue: CommandQueue,
|
command_queue: CommandQueue,
|
||||||
quads_pipeline_state: metal::RenderPipelineState,
|
quads_pipeline_state: metal::RenderPipelineState,
|
||||||
sprites_pipeline_state: metal::RenderPipelineState,
|
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
|
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
unit_vertices: metal::Buffer,
|
unit_vertices: metal::Buffer,
|
||||||
instances: metal::Buffer,
|
instances: metal::Buffer,
|
||||||
monochrome_sprite_atlas: Arc<MetalAtlas>,
|
sprite_atlas: Arc<MetalAtlas>,
|
||||||
polychrome_sprite_atlas: Arc<MetalAtlas>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetalRenderer {
|
impl MetalRenderer {
|
||||||
|
@ -90,43 +90,35 @@ impl MetalRenderer {
|
||||||
"quad_fragment",
|
"quad_fragment",
|
||||||
PIXEL_FORMAT,
|
PIXEL_FORMAT,
|
||||||
);
|
);
|
||||||
|
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
let sprites_pipeline_state = build_pipeline_state(
|
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
"sprites",
|
"monochrome_sprites",
|
||||||
"monochrome_sprite_vertex",
|
"monochrome_sprite_vertex",
|
||||||
"monochrome_sprite_fragment",
|
"monochrome_sprite_fragment",
|
||||||
PIXEL_FORMAT,
|
PIXEL_FORMAT,
|
||||||
);
|
);
|
||||||
|
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"polychrome_sprites",
|
||||||
|
"polychrome_sprite_vertex",
|
||||||
|
"polychrome_sprite_fragment",
|
||||||
|
PIXEL_FORMAT,
|
||||||
|
);
|
||||||
|
|
||||||
let command_queue = device.new_command_queue();
|
let command_queue = device.new_command_queue();
|
||||||
let monochrome_sprite_atlas = Arc::new(MetalAtlas::new(
|
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
|
||||||
Size {
|
|
||||||
width: DevicePixels(1024),
|
|
||||||
height: DevicePixels(768),
|
|
||||||
},
|
|
||||||
MTLPixelFormat::A8Unorm,
|
|
||||||
device.clone(),
|
|
||||||
));
|
|
||||||
let polychrome_sprite_atlas = Arc::new(MetalAtlas::new(
|
|
||||||
Size {
|
|
||||||
width: DevicePixels(1024),
|
|
||||||
height: DevicePixels(768),
|
|
||||||
},
|
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
|
||||||
device.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
layer,
|
layer,
|
||||||
command_queue,
|
command_queue,
|
||||||
quads_pipeline_state,
|
quads_pipeline_state,
|
||||||
sprites_pipeline_state,
|
monochrome_sprites_pipeline_state,
|
||||||
|
polychrome_sprites_pipeline_state,
|
||||||
unit_vertices,
|
unit_vertices,
|
||||||
instances,
|
instances,
|
||||||
monochrome_sprite_atlas,
|
sprite_atlas,
|
||||||
polychrome_sprite_atlas,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,12 +126,8 @@ impl MetalRenderer {
|
||||||
&*self.layer
|
&*self.layer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn monochrome_sprite_atlas(&self) -> &Arc<MetalAtlas> {
|
pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
|
||||||
&self.monochrome_sprite_atlas
|
&self.sprite_atlas
|
||||||
}
|
|
||||||
|
|
||||||
pub fn polychrome_sprite_atlas(&self) -> &Arc<MetalAtlas> {
|
|
||||||
&self.polychrome_sprite_atlas
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &mut Scene) {
|
pub fn draw(&mut self, scene: &mut Scene) {
|
||||||
|
@ -304,41 +292,38 @@ impl MetalRenderer {
|
||||||
}
|
}
|
||||||
align_offset(offset);
|
align_offset(offset);
|
||||||
|
|
||||||
let texture = self.monochrome_sprite_atlas.texture(texture_id);
|
let texture = self.sprite_atlas.texture(texture_id);
|
||||||
let texture_size = size(
|
let texture_size = size(
|
||||||
DevicePixels(texture.width() as i32),
|
DevicePixels(texture.width() as i32),
|
||||||
DevicePixels(texture.height() as i32),
|
DevicePixels(texture.height() as i32),
|
||||||
);
|
);
|
||||||
command_encoder.set_render_pipeline_state(&self.sprites_pipeline_state);
|
command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
|
||||||
command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
MonochromeSpriteInputIndex::Vertices as u64,
|
SpriteInputIndex::Vertices as u64,
|
||||||
Some(&self.unit_vertices),
|
Some(&self.unit_vertices),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
MonochromeSpriteInputIndex::Sprites as u64,
|
SpriteInputIndex::Sprites as u64,
|
||||||
Some(&self.instances),
|
Some(&self.instances),
|
||||||
*offset as u64,
|
*offset as u64,
|
||||||
);
|
);
|
||||||
command_encoder.set_vertex_bytes(
|
command_encoder.set_vertex_bytes(
|
||||||
MonochromeSpriteInputIndex::ViewportSize as u64,
|
SpriteInputIndex::ViewportSize as u64,
|
||||||
mem::size_of_val(&viewport_size) as u64,
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
&viewport_size as *const Size<DevicePixels> as *const _,
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
);
|
);
|
||||||
command_encoder.set_vertex_bytes(
|
command_encoder.set_vertex_bytes(
|
||||||
MonochromeSpriteInputIndex::AtlasTextureSize as u64,
|
SpriteInputIndex::AtlasTextureSize as u64,
|
||||||
mem::size_of_val(&texture_size) as u64,
|
mem::size_of_val(&texture_size) as u64,
|
||||||
&texture_size as *const Size<DevicePixels> as *const _,
|
&texture_size as *const Size<DevicePixels> as *const _,
|
||||||
);
|
);
|
||||||
command_encoder.set_fragment_buffer(
|
command_encoder.set_fragment_buffer(
|
||||||
MonochromeSpriteInputIndex::Sprites as u64,
|
SpriteInputIndex::Sprites as u64,
|
||||||
Some(&self.instances),
|
Some(&self.instances),
|
||||||
*offset as u64,
|
*offset as u64,
|
||||||
);
|
);
|
||||||
command_encoder.set_fragment_texture(
|
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||||
MonochromeSpriteInputIndex::AtlasTexture as u64,
|
|
||||||
Some(&texture),
|
|
||||||
);
|
|
||||||
|
|
||||||
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
|
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
|
||||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
|
@ -373,71 +358,67 @@ impl MetalRenderer {
|
||||||
viewport_size: Size<DevicePixels>,
|
viewport_size: Size<DevicePixels>,
|
||||||
command_encoder: &metal::RenderCommandEncoderRef,
|
command_encoder: &metal::RenderCommandEncoderRef,
|
||||||
) {
|
) {
|
||||||
todo!()
|
if sprites.is_empty() {
|
||||||
// if sprites.is_empty() {
|
return;
|
||||||
// return;
|
}
|
||||||
// }
|
align_offset(offset);
|
||||||
// align_offset(offset);
|
|
||||||
|
|
||||||
// let texture = self.monochrome_sprite_atlas.texture(texture_id);
|
let texture = self.sprite_atlas.texture(texture_id);
|
||||||
// let texture_size = size(
|
let texture_size = size(
|
||||||
// DevicePixels(texture.width() as i32),
|
DevicePixels(texture.width() as i32),
|
||||||
// DevicePixels(texture.height() as i32),
|
DevicePixels(texture.height() as i32),
|
||||||
// );
|
);
|
||||||
// command_encoder.set_render_pipeline_state(&self.sprites_pipeline_state);
|
command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
|
||||||
// command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
// MonochromeSpriteInputIndex::Vertices as u64,
|
SpriteInputIndex::Vertices as u64,
|
||||||
// Some(&self.unit_vertices),
|
Some(&self.unit_vertices),
|
||||||
// 0,
|
0,
|
||||||
// );
|
);
|
||||||
// command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
// MonochromeSpriteInputIndex::Sprites as u64,
|
SpriteInputIndex::Sprites as u64,
|
||||||
// Some(&self.instances),
|
Some(&self.instances),
|
||||||
// *offset as u64,
|
*offset as u64,
|
||||||
// );
|
);
|
||||||
// command_encoder.set_vertex_bytes(
|
command_encoder.set_vertex_bytes(
|
||||||
// MonochromeSpriteInputIndex::ViewportSize as u64,
|
SpriteInputIndex::ViewportSize as u64,
|
||||||
// mem::size_of_val(&viewport_size) as u64,
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
// &viewport_size as *const Size<DevicePixels> as *const _,
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
// );
|
);
|
||||||
// command_encoder.set_vertex_bytes(
|
command_encoder.set_vertex_bytes(
|
||||||
// MonochromeSpriteInputIndex::AtlasTextureSize as u64,
|
SpriteInputIndex::AtlasTextureSize as u64,
|
||||||
// mem::size_of_val(&texture_size) as u64,
|
mem::size_of_val(&texture_size) as u64,
|
||||||
// &texture_size as *const Size<DevicePixels> as *const _,
|
&texture_size as *const Size<DevicePixels> as *const _,
|
||||||
// );
|
);
|
||||||
// command_encoder.set_fragment_buffer(
|
command_encoder.set_fragment_buffer(
|
||||||
// MonochromeSpriteInputIndex::Sprites as u64,
|
SpriteInputIndex::Sprites as u64,
|
||||||
// Some(&self.instances),
|
Some(&self.instances),
|
||||||
// *offset as u64,
|
*offset as u64,
|
||||||
// );
|
);
|
||||||
// command_encoder.set_fragment_texture(
|
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||||
// MonochromeSpriteInputIndex::AtlasTexture as u64,
|
|
||||||
// Some(&texture),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
|
let sprite_bytes_len = mem::size_of::<PolychromeSprite>() * sprites.len();
|
||||||
// let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
// unsafe {
|
unsafe {
|
||||||
// ptr::copy_nonoverlapping(
|
ptr::copy_nonoverlapping(
|
||||||
// sprites.as_ptr() as *const u8,
|
sprites.as_ptr() as *const u8,
|
||||||
// buffer_contents,
|
buffer_contents,
|
||||||
// sprite_bytes_len,
|
sprite_bytes_len,
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let next_offset = *offset + sprite_bytes_len;
|
let next_offset = *offset + sprite_bytes_len;
|
||||||
// assert!(
|
assert!(
|
||||||
// next_offset <= INSTANCE_BUFFER_SIZE,
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
// "instance buffer exhausted"
|
"instance buffer exhausted"
|
||||||
// );
|
);
|
||||||
|
|
||||||
// command_encoder.draw_primitives_instanced(
|
command_encoder.draw_primitives_instanced(
|
||||||
// metal::MTLPrimitiveType::Triangle,
|
metal::MTLPrimitiveType::Triangle,
|
||||||
// 0,
|
0,
|
||||||
// 6,
|
6,
|
||||||
// sprites.len() as u64,
|
sprites.len() as u64,
|
||||||
// );
|
);
|
||||||
// *offset = next_offset;
|
*offset = next_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,7 +470,7 @@ enum QuadInputIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum MonochromeSpriteInputIndex {
|
enum SpriteInputIndex {
|
||||||
Vertices = 0,
|
Vertices = 0,
|
||||||
Sprites = 1,
|
Sprites = 1,
|
||||||
ViewportSize = 2,
|
ViewportSize = 2,
|
||||||
|
|
|
@ -7,7 +7,10 @@ float4 hsla_to_rgba(Hsla hsla);
|
||||||
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||||
Bounds_ScaledPixels clip_bounds,
|
Bounds_ScaledPixels clip_bounds,
|
||||||
constant Size_DevicePixels *viewport_size);
|
constant Size_DevicePixels *viewport_size);
|
||||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds, Corners_ScaledPixels corner_radii);
|
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||||
|
constant Size_DevicePixels *atlas_size);
|
||||||
|
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||||
|
Corners_ScaledPixels corner_radii);
|
||||||
|
|
||||||
struct QuadVertexOutput {
|
struct QuadVertexOutput {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
|
@ -119,27 +122,18 @@ struct MonochromeSpriteVertexOutput {
|
||||||
|
|
||||||
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||||
constant float2 *unit_vertices
|
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||||
[[buffer(MonochromeSpriteInputIndex_Vertices)]],
|
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
constant MonochromeSprite *sprites
|
|
||||||
[[buffer(MonochromeSpriteInputIndex_Sprites)]],
|
|
||||||
constant Size_DevicePixels *viewport_size
|
constant Size_DevicePixels *viewport_size
|
||||||
[[buffer(MonochromeSpriteInputIndex_ViewportSize)]],
|
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||||
constant Size_DevicePixels *atlas_size
|
constant Size_DevicePixels *atlas_size
|
||||||
[[buffer(MonochromeSpriteInputIndex_AtlasTextureSize)]]) {
|
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||||
|
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
MonochromeSprite sprite = sprites[sprite_id];
|
MonochromeSprite sprite = sprites[sprite_id];
|
||||||
float4 device_position = to_device_position(
|
float4 device_position = to_device_position(
|
||||||
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
|
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
|
||||||
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
float2 tile_origin =
|
|
||||||
float2(sprite.tile.bounds.origin.x, sprite.tile.bounds.origin.y);
|
|
||||||
float2 tile_size =
|
|
||||||
float2(sprite.tile.bounds.size.width, sprite.tile.bounds.size.height);
|
|
||||||
float2 tile_position =
|
|
||||||
(tile_origin + unit_vertex * tile_size) /
|
|
||||||
float2((float)atlas_size->width, (float)atlas_size->height);
|
|
||||||
float4 color = hsla_to_rgba(sprite.color);
|
float4 color = hsla_to_rgba(sprite.color);
|
||||||
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
|
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
|
||||||
sprite_id};
|
sprite_id};
|
||||||
|
@ -147,22 +141,60 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||||
|
|
||||||
fragment float4 monochrome_sprite_fragment(
|
fragment float4 monochrome_sprite_fragment(
|
||||||
MonochromeSpriteVertexOutput input [[stage_in]],
|
MonochromeSpriteVertexOutput input [[stage_in]],
|
||||||
constant MonochromeSprite *sprites
|
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
[[buffer(MonochromeSpriteInputIndex_Sprites)]],
|
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||||
texture2d<float> atlas_texture
|
|
||||||
[[texture(MonochromeSpriteInputIndex_AtlasTexture)]]) {
|
|
||||||
MonochromeSprite sprite = sprites[input.sprite_id];
|
MonochromeSprite sprite = sprites[input.sprite_id];
|
||||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||||
min_filter::linear);
|
min_filter::linear);
|
||||||
float4 sample =
|
float4 sample =
|
||||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
float clip_distance =
|
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||||
quad_sdf(input.position.xy, sprite.content_mask.bounds, sprite.content_mask.corner_radii);
|
sprite.content_mask.corner_radii);
|
||||||
float4 color = input.color;
|
float4 color = input.color;
|
||||||
color.a *= sample.a * saturate(0.5 - clip_distance);
|
color.a *= sample.a * saturate(0.5 - clip_distance);
|
||||||
return color;
|
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];
|
||||||
|
float4 device_position = to_device_position(
|
||||||
|
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
|
||||||
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
|
return PolychromeSpriteVertexOutput{device_position, tile_position,
|
||||||
|
sprite_id};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 polychrome_sprite_fragment(
|
||||||
|
PolychromeSpriteVertexOutput input [[stage_in]],
|
||||||
|
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
|
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||||
|
PolychromeSprite sprite = sprites[input.sprite_id];
|
||||||
|
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||||
|
min_filter::linear);
|
||||||
|
float4 sample =
|
||||||
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
|
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||||
|
sprite.content_mask.corner_radii);
|
||||||
|
float4 color = sample;
|
||||||
|
color.a *= saturate(0.5 - clip_distance);
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
float4 hsla_to_rgba(Hsla hsla) {
|
float4 hsla_to_rgba(Hsla hsla) {
|
||||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||||
float s = hsla.s;
|
float s = hsla.s;
|
||||||
|
@ -229,6 +261,14 @@ float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||||
return float4(device_position, 0., 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,
|
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||||
Corners_ScaledPixels corner_radii) {
|
Corners_ScaledPixels corner_radii) {
|
||||||
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
|
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
|
||||||
|
|
|
@ -12,7 +12,11 @@ use core_foundation::{
|
||||||
base::{CFRange, TCFType},
|
base::{CFRange, TCFType},
|
||||||
string::CFString,
|
string::CFString,
|
||||||
};
|
};
|
||||||
use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
|
use core_graphics::{
|
||||||
|
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
|
||||||
|
color_space::CGColorSpace,
|
||||||
|
context::CGContext,
|
||||||
|
};
|
||||||
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
|
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
|
||||||
use font_kit::{
|
use font_kit::{
|
||||||
font::Font as FontKitFont,
|
font::Font as FontKitFont,
|
||||||
|
@ -262,8 +266,22 @@ impl MacTextSystemState {
|
||||||
bitmap_size.height += DevicePixels(1);
|
bitmap_size.height += DevicePixels(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
|
let mut bytes;
|
||||||
let cx = CGContext::create_bitmap_context(
|
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 _),
|
Some(bytes.as_mut_ptr() as *mut _),
|
||||||
bitmap_size.width.0 as usize,
|
bitmap_size.width.0 as usize,
|
||||||
bitmap_size.height.0 as usize,
|
bitmap_size.height.0 as usize,
|
||||||
|
@ -272,6 +290,7 @@ impl MacTextSystemState {
|
||||||
&CGColorSpace::create_device_gray(),
|
&CGColorSpace::create_device_gray(),
|
||||||
kCGImageAlphaOnly,
|
kCGImageAlphaOnly,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Move the origin to bottom left and account for scaling, this
|
// Move the origin to bottom left and account for scaling, this
|
||||||
// makes drawing text consistent with the font-kit's raster_bounds.
|
// makes drawing text consistent with the font-kit's raster_bounds.
|
||||||
|
@ -303,6 +322,17 @@ impl MacTextSystemState {
|
||||||
cx,
|
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))
|
Ok((bitmap_size.into(), bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -886,12 +886,8 @@ impl PlatformWindow for MacWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn monochrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||||
self.0.lock().renderer.monochrome_sprite_atlas().clone()
|
self.0.lock().renderer.sprite_atlas().clone()
|
||||||
}
|
|
||||||
|
|
||||||
fn polychrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
|
||||||
self.0.lock().renderer.polychrome_sprite_atlas().clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{iter::Peekable, mem};
|
use std::{iter::Peekable, mem, slice};
|
||||||
|
|
||||||
use super::{Bounds, Hsla, Point};
|
use super::{Bounds, Hsla, Point};
|
||||||
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
|
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
|
||||||
|
@ -63,42 +63,46 @@ impl SceneLayer {
|
||||||
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
|
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||||
self.quads.sort_unstable();
|
self.quads.sort_unstable();
|
||||||
self.monochrome_sprites.sort_unstable();
|
self.monochrome_sprites.sort_unstable();
|
||||||
|
self.polychrome_sprites.sort_unstable();
|
||||||
BatchIterator::new(
|
BatchIterator {
|
||||||
&self.quads,
|
quads: &self.quads,
|
||||||
self.quads.iter().peekable(),
|
quads_start: 0,
|
||||||
&self.monochrome_sprites,
|
quads_iter: self.quads.iter().peekable(),
|
||||||
self.monochrome_sprites.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, Q, S>
|
struct BatchIterator<'a> {
|
||||||
where
|
|
||||||
Q: Iterator<Item = &'a Quad>,
|
|
||||||
S: Iterator<Item = &'a MonochromeSprite>,
|
|
||||||
{
|
|
||||||
quads: &'a [Quad],
|
quads: &'a [Quad],
|
||||||
sprites: &'a [MonochromeSprite],
|
|
||||||
quads_start: usize,
|
quads_start: usize,
|
||||||
sprites_start: usize,
|
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
||||||
quads_iter: Peekable<Q>,
|
monochrome_sprites: &'a [MonochromeSprite],
|
||||||
sprites_iter: Peekable<S>,
|
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, Q: 'a, S: 'a> Iterator for BatchIterator<'a, Q, S>
|
impl<'a> Iterator for BatchIterator<'a> {
|
||||||
where
|
|
||||||
Q: Iterator<Item = &'a Quad>,
|
|
||||||
S: Iterator<Item = &'a MonochromeSprite>,
|
|
||||||
{
|
|
||||||
type Item = PrimitiveBatch<'a>;
|
type Item = PrimitiveBatch<'a>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut kinds_and_orders = [
|
let mut kinds_and_orders = [
|
||||||
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
|
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
|
||||||
(
|
(
|
||||||
PrimitiveKind::Sprite,
|
PrimitiveKind::MonochromeSprite,
|
||||||
self.sprites_iter.peek().map(|s| s.order),
|
self.monochrome_sprites_iter.peek().map(|s| s.order),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PrimitiveKind::PolychromeSprite,
|
||||||
|
self.polychrome_sprites_iter.peek().map(|s| s.order),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX));
|
kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX));
|
||||||
|
@ -123,53 +127,49 @@ where
|
||||||
self.quads_start = quads_end;
|
self.quads_start = quads_end;
|
||||||
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
|
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
|
||||||
}
|
}
|
||||||
PrimitiveKind::Sprite => {
|
PrimitiveKind::MonochromeSprite => {
|
||||||
let texture_id = self.sprites_iter.peek().unwrap().tile.texture_id;
|
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||||
let sprites_start = self.sprites_start;
|
let sprites_start = self.monochrome_sprites_start;
|
||||||
let sprites_end = sprites_start
|
let sprites_end = sprites_start
|
||||||
+ self
|
+ self
|
||||||
.sprites_iter
|
.monochrome_sprites_iter
|
||||||
.by_ref()
|
.by_ref()
|
||||||
.take_while(|sprite| {
|
.take_while(|sprite| {
|
||||||
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
self.sprites_start = sprites_end;
|
self.monochrome_sprites_start = sprites_end;
|
||||||
Some(PrimitiveBatch::MonochromeSprites {
|
Some(PrimitiveBatch::MonochromeSprites {
|
||||||
texture_id,
|
texture_id,
|
||||||
sprites: &self.sprites[sprites_start..sprites_end],
|
sprites: &self.monochrome_sprites[sprites_start..sprites_end],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
PrimitiveKind::PolychromeSprite => {
|
||||||
|
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||||
|
let sprites_start = self.polychrome_sprites_start;
|
||||||
|
let sprites_end = sprites_start
|
||||||
|
+ self
|
||||||
|
.polychrome_sprites_iter
|
||||||
|
.by_ref()
|
||||||
|
.take_while(|sprite| {
|
||||||
|
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
self.polychrome_sprites_start = sprites_end;
|
||||||
|
Some(PrimitiveBatch::PolychromeSprites {
|
||||||
|
texture_id,
|
||||||
|
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Q: 'a, S: 'a> BatchIterator<'a, Q, S>
|
|
||||||
where
|
|
||||||
Q: Iterator<Item = &'a Quad>,
|
|
||||||
S: Iterator<Item = &'a MonochromeSprite>,
|
|
||||||
{
|
|
||||||
fn new(
|
|
||||||
quads: &'a [Quad],
|
|
||||||
quads_iter: Peekable<Q>,
|
|
||||||
sprites: &'a [MonochromeSprite],
|
|
||||||
sprites_iter: Peekable<S>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
quads,
|
|
||||||
quads_start: 0,
|
|
||||||
quads_iter,
|
|
||||||
sprites,
|
|
||||||
sprites_start: 0,
|
|
||||||
sprites_iter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum PrimitiveKind {
|
pub enum PrimitiveKind {
|
||||||
Quad,
|
Quad,
|
||||||
Sprite,
|
MonochromeSprite,
|
||||||
|
PolychromeSprite,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -36,8 +36,7 @@ pub struct TextSystem {
|
||||||
text_layout_cache: Arc<TextLayoutCache>,
|
text_layout_cache: Arc<TextLayoutCache>,
|
||||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||||
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
||||||
fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
|
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
|
||||||
font_metrics: RwLock<HashMap<Font, FontMetrics>>,
|
|
||||||
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
||||||
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
|
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
|
||||||
}
|
}
|
||||||
|
@ -49,7 +48,6 @@ impl TextSystem {
|
||||||
platform_text_system,
|
platform_text_system,
|
||||||
font_metrics: RwLock::new(HashMap::default()),
|
font_metrics: RwLock::new(HashMap::default()),
|
||||||
font_ids_by_font: RwLock::new(HashMap::default()),
|
font_ids_by_font: RwLock::new(HashMap::default()),
|
||||||
fonts_by_font_id: RwLock::new(HashMap::default()),
|
|
||||||
wrapper_pool: Mutex::new(HashMap::default()),
|
wrapper_pool: Mutex::new(HashMap::default()),
|
||||||
font_runs_pool: Default::default(),
|
font_runs_pool: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -62,30 +60,20 @@ impl TextSystem {
|
||||||
} else {
|
} else {
|
||||||
let font_id = self.platform_text_system.font_id(font)?;
|
let font_id = self.platform_text_system.font_id(font)?;
|
||||||
self.font_ids_by_font.write().insert(font.clone(), font_id);
|
self.font_ids_by_font.write().insert(font.clone(), font_id);
|
||||||
self.fonts_by_font_id.write().insert(font_id, font.clone());
|
|
||||||
Ok(font_id)
|
Ok(font_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
|
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result<Bounds<Pixels>> {
|
||||||
self.fonts_by_font_id
|
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
|
||||||
.read()
|
|
||||||
.get(&font_id)
|
|
||||||
.ok_or_else(|| anyhow!("font not found"))
|
|
||||||
.map(|font| f(self, font))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
|
|
||||||
self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typographic_bounds(
|
pub fn typographic_bounds(
|
||||||
&self,
|
&self,
|
||||||
font: &Font,
|
font_id: FontId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
character: char,
|
character: char,
|
||||||
) -> Result<Bounds<Pixels>> {
|
) -> Result<Bounds<Pixels>> {
|
||||||
let font_id = self.font_id(font)?;
|
|
||||||
let glyph_id = self
|
let glyph_id = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.glyph_for_char(font_id, character)
|
.glyph_for_char(font_id, character)
|
||||||
|
@ -93,65 +81,63 @@ impl TextSystem {
|
||||||
let bounds = self
|
let bounds = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.typographic_bounds(font_id, glyph_id)?;
|
.typographic_bounds(font_id, glyph_id)?;
|
||||||
self.read_metrics(font, |metrics| {
|
self.read_metrics(font_id, |metrics| {
|
||||||
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
|
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
|
pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
|
||||||
let font_id = self.font_id(font)?;
|
|
||||||
let glyph_id = self
|
let glyph_id = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.glyph_for_char(font_id, ch)
|
.glyph_for_char(font_id, ch)
|
||||||
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
||||||
let result =
|
let result = self.platform_text_system.advance(font_id, glyph_id)?
|
||||||
self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32;
|
/ self.units_per_em(font_id)? as f32;
|
||||||
|
|
||||||
Ok(result * font_size)
|
Ok(result * font_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn units_per_em(&self, font: &Font) -> Result<u32> {
|
pub fn units_per_em(&self, font_id: FontId) -> Result<u32> {
|
||||||
self.read_metrics(font, |metrics| metrics.units_per_em as u32)
|
self.read_metrics(font_id, |metrics| metrics.units_per_em as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.cap_height(font_size))
|
self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.x_height(font_size))
|
self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.ascent(font_size))
|
self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
|
pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
|
||||||
self.read_metrics(font, |metrics| metrics.descent(font_size))
|
self.read_metrics(font_id, |metrics| metrics.descent(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn baseline_offset(
|
pub fn baseline_offset(
|
||||||
&self,
|
&self,
|
||||||
font: &Font,
|
font_id: FontId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
) -> Result<Pixels> {
|
) -> Result<Pixels> {
|
||||||
let ascent = self.ascent(font, font_size)?;
|
let ascent = self.ascent(font_id, font_size)?;
|
||||||
let descent = self.descent(font, font_size)?;
|
let descent = self.descent(font_id, font_size)?;
|
||||||
let padding_top = (line_height - ascent - descent) / 2.;
|
let padding_top = (line_height - ascent - descent) / 2.;
|
||||||
Ok(padding_top + ascent)
|
Ok(padding_top + ascent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
|
fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
|
||||||
let lock = self.font_metrics.upgradable_read();
|
let lock = self.font_metrics.upgradable_read();
|
||||||
|
|
||||||
if let Some(metrics) = lock.get(font) {
|
if let Some(metrics) = lock.get(&font_id) {
|
||||||
Ok(read(metrics))
|
Ok(read(metrics))
|
||||||
} else {
|
} else {
|
||||||
let font_id = self.platform_text_system.font_id(&font)?;
|
|
||||||
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
||||||
let metrics = lock
|
let metrics = lock
|
||||||
.entry(font.clone())
|
.entry(font_id)
|
||||||
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
|
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
|
||||||
Ok(read(metrics))
|
Ok(read(metrics))
|
||||||
}
|
}
|
||||||
|
@ -390,6 +376,7 @@ pub struct RenderGlyphParams {
|
||||||
pub(crate) font_size: Pixels,
|
pub(crate) font_size: Pixels,
|
||||||
pub(crate) subpixel_variant: Point<u8>,
|
pub(crate) subpixel_variant: Point<u8>,
|
||||||
pub(crate) scale_factor: f32,
|
pub(crate) scale_factor: f32,
|
||||||
|
pub(crate) is_emoji: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for RenderGlyphParams {}
|
impl Eq for RenderGlyphParams {}
|
||||||
|
|
|
@ -109,8 +109,10 @@ impl Line {
|
||||||
let text_system = cx.text_system().clone();
|
let text_system = cx.text_system().clone();
|
||||||
|
|
||||||
for run in &self.layout.runs {
|
for run in &self.layout.runs {
|
||||||
text_system.with_font(run.font_id, |system, font| {
|
let max_glyph_width = text_system
|
||||||
let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width;
|
.bounding_box(run.font_id, self.layout.font_size)?
|
||||||
|
.size
|
||||||
|
.width;
|
||||||
|
|
||||||
for glyph in &run.glyphs {
|
for glyph in &run.glyphs {
|
||||||
let glyph_origin = origin + baseline_offset + glyph.position;
|
let glyph_origin = origin + baseline_offset + glyph.position;
|
||||||
|
@ -130,9 +132,7 @@ impl Line {
|
||||||
underline.get_or_insert((
|
underline.get_or_insert((
|
||||||
point(
|
point(
|
||||||
glyph_origin.x,
|
glyph_origin.x,
|
||||||
origin.y
|
origin.y + baseline_offset.y + (self.layout.descent * 0.618),
|
||||||
+ baseline_offset.y
|
|
||||||
+ (self.layout.descent * 0.618),
|
|
||||||
),
|
),
|
||||||
UnderlineStyle {
|
UnderlineStyle {
|
||||||
color: style_run.underline.color,
|
color: style_run.underline.color,
|
||||||
|
@ -177,9 +177,6 @@ impl Line {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_underline_start, _underline_style)) = underline.take() {
|
if let Some((_underline_start, _underline_style)) = underline.take() {
|
||||||
|
@ -281,10 +278,12 @@ impl Line {
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.text_system().with_font(run.font_id, |system, font| {
|
let text_system = cx.text_system();
|
||||||
let _glyph_bounds = Bounds {
|
let _glyph_bounds = Bounds {
|
||||||
origin: glyph_origin,
|
origin: glyph_origin,
|
||||||
size: system.bounding_box(font, self.layout.font_size)?.size,
|
size: text_system
|
||||||
|
.bounding_box(run.font_id, self.layout.font_size)?
|
||||||
|
.size,
|
||||||
};
|
};
|
||||||
// if glyph_bounds.intersects(visible_bounds) {
|
// if glyph_bounds.intersects(visible_bounds) {
|
||||||
// if glyph.is_emoji {
|
// if glyph.is_emoji {
|
||||||
|
@ -304,8 +303,6 @@ impl Line {
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,7 @@ pub struct AnyWindow {}
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
||||||
monochrome_sprite_atlas: Arc<dyn PlatformAtlas>,
|
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||||
polychrome_sprite_atlas: Arc<dyn PlatformAtlas>,
|
|
||||||
rem_size: Pixels,
|
rem_size: Pixels,
|
||||||
content_size: Size<Pixels>,
|
content_size: Size<Pixels>,
|
||||||
layout_engine: TaffyLayoutEngine,
|
layout_engine: TaffyLayoutEngine,
|
||||||
|
@ -36,8 +35,7 @@ impl Window {
|
||||||
cx: &mut MainThread<AppContext>,
|
cx: &mut MainThread<AppContext>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let platform_window = cx.platform().open_window(handle, options);
|
let platform_window = cx.platform().open_window(handle, options);
|
||||||
let monochrome_sprite_atlas = platform_window.monochrome_sprite_atlas();
|
let sprite_atlas = platform_window.sprite_atlas();
|
||||||
let polychrome_sprite_atlas = platform_window.polychrome_sprite_atlas();
|
|
||||||
let mouse_position = platform_window.mouse_position();
|
let mouse_position = platform_window.mouse_position();
|
||||||
let content_size = platform_window.content_size();
|
let content_size = platform_window.content_size();
|
||||||
let scale_factor = platform_window.scale_factor();
|
let scale_factor = platform_window.scale_factor();
|
||||||
|
@ -60,8 +58,7 @@ impl Window {
|
||||||
Window {
|
Window {
|
||||||
handle,
|
handle,
|
||||||
platform_window,
|
platform_window,
|
||||||
monochrome_sprite_atlas,
|
sprite_atlas,
|
||||||
polychrome_sprite_atlas,
|
|
||||||
rem_size: px(16.),
|
rem_size: px(16.),
|
||||||
content_size,
|
content_size,
|
||||||
layout_engine: TaffyLayoutEngine::new(),
|
layout_engine: TaffyLayoutEngine::new(),
|
||||||
|
@ -231,6 +228,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
font_size,
|
font_size,
|
||||||
subpixel_variant,
|
subpixel_variant,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
|
is_emoji: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||||
|
@ -238,7 +236,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
let layer_id = self.current_layer_id();
|
let layer_id = self.current_layer_id();
|
||||||
let tile = self
|
let tile = self
|
||||||
.window
|
.window
|
||||||
.monochrome_sprite_atlas
|
.sprite_atlas
|
||||||
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
self.text_system().rasterize_glyph(¶ms)
|
self.text_system().rasterize_glyph(¶ms)
|
||||||
})?;
|
})?;
|
||||||
|
@ -262,6 +260,54 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paint_emoji(
|
||||||
|
&mut self,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
order: u32,
|
||||||
|
font_id: FontId,
|
||||||
|
glyph_id: GlyphId,
|
||||||
|
font_size: Pixels,
|
||||||
|
) -> Result<()> {
|
||||||
|
let scale_factor = self.scale_factor();
|
||||||
|
let glyph_origin = origin.scale(scale_factor);
|
||||||
|
let params = RenderGlyphParams {
|
||||||
|
font_id,
|
||||||
|
glyph_id,
|
||||||
|
font_size,
|
||||||
|
// We don't render emojis with subpixel variants.
|
||||||
|
subpixel_variant: Default::default(),
|
||||||
|
scale_factor,
|
||||||
|
is_emoji: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||||
|
if !raster_bounds.is_zero() {
|
||||||
|
let layer_id = self.current_layer_id();
|
||||||
|
let tile = self
|
||||||
|
.window
|
||||||
|
.sprite_atlas
|
||||||
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
|
self.text_system().rasterize_glyph(¶ms)
|
||||||
|
})?;
|
||||||
|
let bounds = Bounds {
|
||||||
|
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
|
||||||
|
size: tile.bounds.size.map(Into::into),
|
||||||
|
};
|
||||||
|
let content_mask = self.content_mask().scale(scale_factor);
|
||||||
|
|
||||||
|
self.window.scene.insert(
|
||||||
|
layer_id,
|
||||||
|
PolychromeSprite {
|
||||||
|
order,
|
||||||
|
bounds,
|
||||||
|
content_mask,
|
||||||
|
tile,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn paint_svg(
|
pub fn paint_svg(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
|
@ -280,13 +326,13 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let layer_id = self.current_layer_id();
|
let layer_id = self.current_layer_id();
|
||||||
let tile = self.window.monochrome_sprite_atlas.get_or_insert_with(
|
let tile =
|
||||||
¶ms.clone().into(),
|
self.window
|
||||||
&mut || {
|
.sprite_atlas
|
||||||
|
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||||
let bytes = self.svg_renderer.render(¶ms)?;
|
let bytes = self.svg_renderer.render(¶ms)?;
|
||||||
Ok((params.size, bytes))
|
Ok((params.size, bytes))
|
||||||
},
|
})?;
|
||||||
)?;
|
|
||||||
let content_mask = self.content_mask().scale(scale_factor);
|
let content_mask = self.content_mask().scale(scale_factor);
|
||||||
|
|
||||||
self.window.scene.insert(
|
self.window.scene.insert(
|
||||||
|
@ -303,52 +349,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_emoji(
|
|
||||||
&mut self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
order: u32,
|
|
||||||
font_id: FontId,
|
|
||||||
glyph_id: GlyphId,
|
|
||||||
font_size: Pixels,
|
|
||||||
) -> Result<()> {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let glyph_origin = origin.scale(scale_factor);
|
|
||||||
let params = RenderGlyphParams {
|
|
||||||
font_id,
|
|
||||||
glyph_id,
|
|
||||||
font_size,
|
|
||||||
subpixel_variant: Default::default(),
|
|
||||||
scale_factor,
|
|
||||||
};
|
|
||||||
|
|
||||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
|
||||||
if !raster_bounds.is_zero() {
|
|
||||||
let layer_id = self.current_layer_id();
|
|
||||||
let tile = self
|
|
||||||
.window
|
|
||||||
.polychrome_sprite_atlas
|
|
||||||
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
|
||||||
self.text_system().rasterize_glyph(¶ms)
|
|
||||||
})?;
|
|
||||||
let bounds = Bounds {
|
|
||||||
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
|
|
||||||
size: tile.bounds.size.map(Into::into),
|
|
||||||
};
|
|
||||||
let content_mask = self.content_mask().scale(scale_factor);
|
|
||||||
|
|
||||||
self.window.scene.insert(
|
|
||||||
layer_id,
|
|
||||||
PolychromeSprite {
|
|
||||||
order,
|
|
||||||
bounds,
|
|
||||||
content_mask,
|
|
||||||
tile,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn draw(&mut self) -> Result<()> {
|
pub(crate) fn draw(&mut self) -> Result<()> {
|
||||||
let unit_entity = self.unit_entity.clone();
|
let unit_entity = self.unit_entity.clone();
|
||||||
self.update_entity(&unit_entity, |_, cx| {
|
self.update_entity(&unit_entity, |_, cx| {
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl CollabPanel {
|
||||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
||||||
// .group()
|
// .group()
|
||||||
// List Section Header
|
// List Section Header
|
||||||
.child(self.list_section_header("#CRDB", true, theme))
|
.child(self.list_section_header("#CRDB 🗃️", true, theme))
|
||||||
// List Item Large
|
// List Item Large
|
||||||
.child(self.list_item(
|
.child(self.list_item(
|
||||||
"http://github.com/maxbrunsfeld.png?s=50",
|
"http://github.com/maxbrunsfeld.png?s=50",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue