1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 15:04:36 +03:00

wayland: render title with our font layer when doing CSD

This isn't perfect by any means:

* Should allow configuring a sans-serif font
* Emoji need to be scaled

but it allows us to upgrade SCTK without loosing the titlebar
or any control over client side decorations.
This commit is contained in:
Wez Furlong 2021-06-26 22:47:43 -07:00
parent 4f9532f9d8
commit 282c148bf6

View File

@ -10,13 +10,16 @@ use std::cell::RefCell;
use std::cmp::max;
use std::rc::Rc;
use std::sync::Mutex;
use tiny_skia::{FillRule, Paint, PathBuilder, PixmapMut, Rect, Stroke, Transform};
use tiny_skia::{
ColorU8, FillRule, Paint, PathBuilder, PixmapMut, PixmapPaint, PixmapRef, Rect, Stroke,
Transform,
};
use wayland_client::protocol::{
wl_compositor, wl_output, wl_pointer, wl_seat, wl_shm, wl_subcompositor, wl_subsurface,
wl_surface,
};
use wayland_client::{Attached, DispatchData, Main};
use wezterm_font::FontConfiguration;
use wezterm_font::{FontConfiguration, FontMetrics, GlyphInfo, RasterizedGlyph};
/// Unambiguous representation of an ARGB color
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -597,6 +600,93 @@ pub struct ConceptFrame {
surface_version: u32,
config: ConceptConfig,
title: Option<String>,
shaped_title: Option<ShapedTitle>,
}
struct ShapedTitle {
title: String,
glyphs: Vec<ShapedGlyph>,
metrics: FontMetrics,
state: WindowState,
}
struct ShapedGlyph {
info: GlyphInfo,
glyph: RasterizedGlyph,
}
impl ConceptFrame {
fn reshape_title(&mut self) -> Option<()> {
let font_config = self.config.font_config.as_ref()?;
let title = self.title.as_ref().map(|s| s.as_str()).unwrap_or("");
if title.is_empty() {
self.title.take();
self.shaped_title.take();
return Some(());
}
if let Some(existing) = self.shaped_title.as_ref() {
if existing.title == title && existing.state == self.active {
return Some(());
}
}
let font = font_config.default_font().ok()?;
let metrics = font.metrics();
let infos = font
.shape(
title,
|| {
// TODO: font fallback completed, trigger title repaint!
},
|_| {
// We don't do synthesis here, so no need to filter
},
)
.ok()?;
let mut glyphs = vec![];
let title_color = self.config.title_color.get_for(self.active);
for info in infos {
if let Ok(mut glyph) = font.rasterize_glyph(info.glyph_pos, info.font_idx) {
// fixup colors: they need to be switched to the appropriate
// pixel format, and for monochrome font data we need to tint
// it with their preferred title color
if let Some(mut data) =
PixmapMut::from_bytes(&mut glyph.data, glyph.width as u32, glyph.height as u32)
{
for p in data.pixels_mut() {
let c = p.demultiply();
let (r, g, b, a) = (c.red(), c.green(), c.blue(), c.alpha());
if glyph.has_color {
*p = ColorU8::from_rgba(b, g, r, a).premultiply();
} else {
// Apply the preferred title color
*p = ColorU8::from_rgba(
((b as f32 / 255.) * (title_color.r as f32 / 255.) * 255.) as u8,
((g as f32 / 255.) * (title_color.g as f32 / 255.) * 255.) as u8,
((r as f32 / 255.) * (title_color.b as f32 / 255.) * 255.) as u8,
((a as f32 / 255.) * (title_color.a as f32 / 255.) * 255.) as u8,
)
.premultiply();
}
}
}
glyphs.push(ShapedGlyph { info, glyph });
}
}
self.shaped_title.replace(ShapedTitle {
title: title.to_string(),
glyphs,
metrics,
state: self.active,
});
Some(())
}
}
impl Frame for ConceptFrame {
@ -650,6 +740,7 @@ impl Frame for ConceptFrame {
surface_version: compositor.as_ref().version(),
config: ConceptConfig::default(),
title: None,
shaped_title: None,
})
}
@ -844,6 +935,7 @@ impl Frame for ConceptFrame {
}
fn redraw(&mut self) {
self.reshape_title();
let inner = self.inner.borrow_mut();
// Don't draw borders if the frame explicitly hidden or fullscreened.
@ -922,6 +1014,36 @@ impl Frame for ConceptFrame {
None,
);
if let Some(shaped) = self.shaped_title.as_ref() {
let mut x = 8.;
let identity = Transform::identity();
let paint = PixmapPaint::default();
for item in &shaped.glyphs {
if let Some(data) = PixmapRef::from_bytes(
&item.glyph.data,
item.glyph.width as u32,
item.glyph.height as u32,
) {
// FIXME: scale emoji
pixmap.draw_pixmap(
(x + item.info.x_offset.get() + item.glyph.bearing_x.get())
as i32,
(HEADER_SIZE * 3 / 4) as i32
+ (shaped.metrics.descender
- (item.info.y_offset + item.glyph.bearing_y))
.get() as i32,
data,
&paint,
identity,
None,
);
}
x += item.info.x_advance.get();
}
}
draw_buttons(
&mut pixmap,
width,
@ -943,77 +1065,6 @@ impl Frame for ConceptFrame {
.collect::<Vec<Location>>(),
&self.config,
);
/*
if let Some((ref font_face, font_size)) = self.config.title_font {
if let Some(title) = self.title.clone() {
// If theres no stored font data, find the first ttf regular sans font and
// store it
if self.font_data.is_none() {
if let Some(font) = fontconfig::FontConfig::new()
.unwrap()
.get_regular_family_fonts(&font_face)
.unwrap()
.iter()
.find(|p| p.extension().map(|e| e == "ttf").unwrap_or(false))
{
let mut font_data = Vec::new();
if let Ok(mut file) = ::std::fs::File::open(font) {
match file.read_to_end(&mut font_data) {
Ok(_) => self.font_data = Some(font_data),
Err(err) => {
log::error!("Could not read font file: {}", err)
}
}
}
}
}
// Create text from stored title and font data
if let Some(ref font_data) = self.font_data {
let title_color = self.config.title_color.get_for(self.active);
let mut title_text = text::Text::new(
(
0,
(HEADER_SIZE as usize / 2)
.saturating_sub((font_size / 2.0).ceil() as usize)
* header_scale as usize,
),
title_color.into(),
font_data,
font_size * header_scale as f32,
1.0,
title,
);
let mut button_count = 0isize;
if self.config.close_button.is_some() {
button_count += 1;
}
if self.config.maximize_button.is_some() {
button_count += 1;
}
if self.config.minimize_button.is_some() {
button_count += 1;
}
let scaled_button_size =
HEADER_SIZE as isize * header_scale as isize;
let button_space = button_count * scaled_button_size;
let scaled_header_width = width as isize * header_scale as isize;
// Check if text is bigger then the available width
if (scaled_header_width - button_space)
> (title_text.get_width() as isize + scaled_button_size)
{
title_text.pos.0 =
(scaled_header_width - button_space) as usize / 2
- (title_text.get_width() / 2);
header_canvas.draw(&title_text);
}
}
}
}
*/
}
// For each pixel in borders