egui-glyphon/src/lib.rs

220 lines
6.7 KiB
Rust
Raw Permalink Normal View History

2024-02-05 09:30:04 -05:00
//! This crate is for using [`glyphon`] to render advanced shaped text to the screen in an [`egui`] application
//! Please see the example for a primer on how to use this crate
2024-03-06 19:34:52 -05:00
use std::ops::DerefMut;
2024-02-05 09:30:04 -05:00
use std::sync::Arc;
use egui::mutex::{Mutex, RwLock};
use egui::{Pos2, Rect, Vec2};
use egui_wgpu::wgpu;
2024-02-05 11:19:45 -05:00
use egui_wgpu::ScreenDescriptor;
2024-02-05 09:30:04 -05:00
use glyphon::{
2024-10-11 14:02:27 -04:00
Buffer, Color, ColorMode, FontSystem, PrepareError, Resolution, SwashCache, TextArea,
TextAtlas, TextBounds, TextRenderer,
2024-02-05 09:30:04 -05:00
};
pub use glyphon;
/// A text buffer with some accosiated data used to construect a [`glyphon::TextArea`]
2024-10-11 14:02:27 -04:00
pub struct BufferWithTextArea<T> {
2024-03-06 19:34:52 -05:00
pub buffer: Arc<RwLock<Buffer>>,
2024-02-05 09:30:04 -05:00
pub rect: Rect,
pub scale: f32,
pub opacity: f32,
pub default_color: Color,
2024-10-11 14:02:27 -04:00
pub associated_data: T,
2024-02-05 09:30:04 -05:00
}
/// Use this function to find out the dimensions of a buffer, translate the resulting rect and use it in [`BufferWithTextArea::new`]
pub fn measure_buffer(buffer: &Buffer) -> Rect {
let mut rtl = false;
let (width, total_lines) =
buffer
.layout_runs()
.fold((0.0, 0usize), |(width, total_lines), run| {
if run.rtl {
rtl = true;
}
(run.line_w.max(width), total_lines + 1)
});
let (max_width, max_height) = buffer.size();
Rect::from_min_size(
Pos2::ZERO,
Vec2::new(
2024-10-11 14:02:27 -04:00
if rtl {
max_width.unwrap_or(width)
} else {
width.min(max_width.unwrap_or(width))
},
(total_lines as f32 * buffer.metrics().line_height).min(max_height.unwrap_or(f32::MAX)),
2024-02-05 09:30:04 -05:00
),
)
}
2024-10-11 14:02:27 -04:00
impl<T> BufferWithTextArea<T> {
2024-02-05 09:30:04 -05:00
pub fn new(
2024-03-06 19:34:52 -05:00
buffer: Arc<RwLock<Buffer>>,
2024-02-05 09:30:04 -05:00
rect: Rect,
opacity: f32,
default_color: Color,
ctx: &egui::Context,
2024-10-11 14:02:27 -04:00
associated_data: T,
2024-02-05 09:30:04 -05:00
) -> Self {
let ppi = ctx.pixels_per_point();
let rect = rect * ppi;
BufferWithTextArea {
buffer,
rect,
scale: ppi,
opacity,
default_color,
2024-10-11 14:02:27 -04:00
associated_data,
2024-02-05 09:30:04 -05:00
}
}
}
/// A type which must be inserted into the [`egui_wgpu::RenderState`] before any text rendering can happen. Do this with [`GlyphonRenderer::insert`]
pub struct GlyphonRenderer {
font_system: Arc<Mutex<FontSystem>>,
cache: SwashCache,
atlas: TextAtlas,
text_renderer: TextRenderer,
2024-10-11 14:02:27 -04:00
viewport: glyphon::Viewport,
2024-02-05 09:30:04 -05:00
}
impl GlyphonRenderer {
/// Insert an instance of itself into the [`egui_wgpu::RenderState`]
2024-10-11 14:02:27 -04:00
pub fn insert(
wgpu_render_state: &egui_wgpu::RenderState,
font_system: Arc<Mutex<FontSystem>>,
glyphon_cache: &glyphon::Cache,
glyphon_viewport: glyphon::Viewport,
) {
2024-02-05 09:30:04 -05:00
let device = &wgpu_render_state.device;
let queue = &wgpu_render_state.queue;
let cache = SwashCache::new();
let mut atlas = TextAtlas::with_color_mode(
device,
queue,
2024-10-11 14:02:27 -04:00
glyphon_cache,
2024-02-05 09:30:04 -05:00
wgpu_render_state.target_format,
2024-03-06 19:34:52 -05:00
ColorMode::Web,
2024-02-05 09:30:04 -05:00
);
let text_renderer =
TextRenderer::new(&mut atlas, device, wgpu::MultisampleState::default(), None);
wgpu_render_state
.renderer
.write()
.callback_resources
.insert(Self {
font_system: Arc::clone(&font_system),
cache,
atlas,
text_renderer,
2024-10-11 14:02:27 -04:00
viewport: glyphon_viewport,
2024-02-05 09:30:04 -05:00
});
}
2024-03-06 19:34:52 -05:00
fn prepare<'a>(
2024-02-05 09:30:04 -05:00
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
screen_resolution: Resolution,
2024-03-06 19:34:52 -05:00
text_areas: impl IntoIterator<Item = TextArea<'a>>,
2024-02-05 09:30:04 -05:00
) -> Result<(), PrepareError> {
2024-10-11 14:02:27 -04:00
self.viewport.update(queue, screen_resolution);
2024-02-05 09:30:04 -05:00
self.text_renderer.prepare(
device,
queue,
self.font_system.lock().deref_mut(),
&mut self.atlas,
2024-10-11 14:02:27 -04:00
&self.viewport,
2024-02-05 09:30:04 -05:00
text_areas,
&mut self.cache,
)
}
}
/// A callback which can be put into an [`egui_wgpu::renderer::Callback`].
// And wrapped with an [`egui::PaintCallback`]. Only add one callback per individual
// deffered viewport.
2024-10-11 14:02:27 -04:00
pub struct GlyphonRendererCallback<T> {
2024-02-05 09:30:04 -05:00
/// These buffers will be rendered to the screen all at the same time on the same layer.
2024-10-11 14:02:27 -04:00
pub buffers: Vec<BufferWithTextArea<T>>,
2024-02-05 09:30:04 -05:00
}
2024-10-11 14:02:27 -04:00
impl<T: Sync + Send> egui_wgpu::CallbackTrait for GlyphonRendererCallback<T> {
2024-02-05 09:30:04 -05:00
fn prepare(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
screen_descriptor: &ScreenDescriptor,
_egui_encoder: &mut wgpu::CommandEncoder,
resources: &mut egui_wgpu::CallbackResources,
) -> Vec<wgpu::CommandBuffer> {
let glyphon_renderer: &mut GlyphonRenderer = resources.get_mut().unwrap();
glyphon_renderer.atlas.trim();
2024-03-06 19:34:52 -05:00
let bufrefs: Vec<_> = self.buffers.iter().map(|b| b.buffer.read()).collect();
let text_areas: Vec<_> = self
.buffers
.iter()
.enumerate()
.map(|(i, b)| TextArea {
buffer: bufrefs.get(i).unwrap(),
left: b.rect.left(),
top: b.rect.top(),
scale: b.scale,
bounds: TextBounds {
left: b.rect.left() as i32,
top: b.rect.top() as i32,
right: b.rect.right() as i32,
bottom: b.rect.bottom() as i32,
},
default_color: b.default_color,
2024-10-11 14:02:27 -04:00
custom_glyphs: &[],
opacity: b.opacity,
2024-03-06 19:34:52 -05:00
})
.collect();
2024-02-05 09:30:04 -05:00
glyphon_renderer
.prepare(
device,
queue,
Resolution {
width: screen_descriptor.size_in_pixels[0],
height: screen_descriptor.size_in_pixels[1],
},
2024-03-06 19:34:52 -05:00
text_areas,
2024-02-05 09:30:04 -05:00
)
.unwrap();
Vec::new()
}
2024-10-11 14:02:27 -04:00
fn paint(
2024-02-05 09:30:04 -05:00
&self,
info: egui::PaintCallbackInfo,
2024-10-11 14:02:27 -04:00
render_pass: &mut wgpu::RenderPass<'static>,
callback_resources: &egui_wgpu::CallbackResources,
2024-02-05 09:30:04 -05:00
) {
render_pass.set_viewport(
0.0,
0.0,
info.screen_size_px[0] as f32,
info.screen_size_px[1] as f32,
0.0,
1.0,
);
2024-10-11 14:02:27 -04:00
let glyphon_renderer: &GlyphonRenderer = callback_resources.get().unwrap();
glyphon_renderer
.text_renderer
.render(
&glyphon_renderer.atlas,
&glyphon_renderer.viewport,
render_pass,
)
.unwrap()
2024-02-05 09:30:04 -05:00
}
}