mirror of
https://github.com/wez/wezterm.git
synced 2024-12-29 00:21:57 +03:00
refactor: split render.rs into smaller modules
This commit is contained in:
parent
edb6aa38f2
commit
5838b2c98b
@ -1,6 +1,6 @@
|
||||
use crate::termwindow::box_model::*;
|
||||
use crate::termwindow::modal::Modal;
|
||||
use crate::termwindow::render::{
|
||||
use crate::termwindow::render::corners::{
|
||||
BOTTOM_LEFT_ROUNDED_CORNER, BOTTOM_RIGHT_ROUNDED_CORNER, TOP_LEFT_ROUNDED_CORNER,
|
||||
TOP_RIGHT_ROUNDED_CORNER,
|
||||
};
|
||||
|
@ -638,7 +638,7 @@ impl TermWindow {
|
||||
dpi,
|
||||
};
|
||||
|
||||
let border = Self::get_os_order_impl(&None, &config, &dimensions, &render_metrics);
|
||||
let border = Self::get_os_border_impl(&None, &config, &dimensions, &render_metrics);
|
||||
|
||||
dimensions.pixel_height += (border.top + border.bottom).get() as usize;
|
||||
dimensions.pixel_width += (border.left + border.right).get() as usize;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::{CommandDef, ExpandedCommand};
|
||||
use crate::termwindow::box_model::*;
|
||||
use crate::termwindow::modal::Modal;
|
||||
use crate::termwindow::render::{
|
||||
use crate::termwindow::render::corners::{
|
||||
BOTTOM_LEFT_ROUNDED_CORNER, BOTTOM_RIGHT_ROUNDED_CORNER, TOP_LEFT_ROUNDED_CORNER,
|
||||
TOP_RIGHT_ROUNDED_CORNER,
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::termwindow::box_model::*;
|
||||
use crate::termwindow::modal::Modal;
|
||||
use crate::termwindow::render::{
|
||||
use crate::termwindow::render::corners::{
|
||||
BOTTOM_LEFT_ROUNDED_CORNER, BOTTOM_RIGHT_ROUNDED_CORNER, TOP_LEFT_ROUNDED_CORNER,
|
||||
TOP_RIGHT_ROUNDED_CORNER,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
148
wezterm-gui/src/termwindow/render/borders.rs
Normal file
148
wezterm-gui/src/termwindow/render/borders.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use crate::quad::TripleLayerQuadAllocator;
|
||||
use crate::utilsprites::RenderMetrics;
|
||||
use ::window::ULength;
|
||||
use config::{ConfigHandle, DimensionContext};
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn paint_window_borders(
|
||||
&mut self,
|
||||
layers: &mut TripleLayerQuadAllocator,
|
||||
) -> anyhow::Result<()> {
|
||||
let border_dimensions = self.get_os_border();
|
||||
|
||||
if border_dimensions.top.get() > 0
|
||||
|| border_dimensions.bottom.get() > 0
|
||||
|| border_dimensions.left.get() > 0
|
||||
|| border_dimensions.right.get() > 0
|
||||
{
|
||||
let height = self.dimensions.pixel_height as f32;
|
||||
let width = self.dimensions.pixel_width as f32;
|
||||
|
||||
let border_top = border_dimensions.top.get() as f32;
|
||||
if border_top > 0.0 {
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
1,
|
||||
euclid::rect(0.0, 0.0, width, border_top),
|
||||
self.config
|
||||
.window_frame
|
||||
.border_top_color
|
||||
.map(|c| c.to_linear())
|
||||
.unwrap_or(border_dimensions.color),
|
||||
)?;
|
||||
}
|
||||
|
||||
let border_left = border_dimensions.left.get() as f32;
|
||||
if border_left > 0.0 {
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
1,
|
||||
euclid::rect(0.0, 0.0, border_left, height),
|
||||
self.config
|
||||
.window_frame
|
||||
.border_left_color
|
||||
.map(|c| c.to_linear())
|
||||
.unwrap_or(border_dimensions.color),
|
||||
)?;
|
||||
}
|
||||
|
||||
let border_bottom = border_dimensions.bottom.get() as f32;
|
||||
if border_bottom > 0.0 {
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
1,
|
||||
euclid::rect(0.0, height - border_bottom, width, height),
|
||||
self.config
|
||||
.window_frame
|
||||
.border_bottom_color
|
||||
.map(|c| c.to_linear())
|
||||
.unwrap_or(border_dimensions.color),
|
||||
)?;
|
||||
}
|
||||
|
||||
let border_right = border_dimensions.right.get() as f32;
|
||||
if border_right > 0.0 {
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
1,
|
||||
euclid::rect(width - border_right, 0.0, border_right, height),
|
||||
self.config
|
||||
.window_frame
|
||||
.border_right_color
|
||||
.map(|c| c.to_linear())
|
||||
.unwrap_or(border_dimensions.color),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_os_border_impl(
|
||||
os_parameters: &Option<window::parameters::Parameters>,
|
||||
config: &ConfigHandle,
|
||||
dimensions: &crate::Dimensions,
|
||||
render_metrics: &RenderMetrics,
|
||||
) -> window::parameters::Border {
|
||||
let mut border = os_parameters
|
||||
.as_ref()
|
||||
.and_then(|p| p.border_dimensions.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
border.left += ULength::new(
|
||||
config
|
||||
.window_frame
|
||||
.border_left_width
|
||||
.evaluate_as_pixels(DimensionContext {
|
||||
dpi: dimensions.dpi as f32,
|
||||
pixel_max: dimensions.pixel_width as f32,
|
||||
pixel_cell: render_metrics.cell_size.width as f32,
|
||||
})
|
||||
.ceil() as usize,
|
||||
);
|
||||
border.right += ULength::new(
|
||||
config
|
||||
.window_frame
|
||||
.border_right_width
|
||||
.evaluate_as_pixels(DimensionContext {
|
||||
dpi: dimensions.dpi as f32,
|
||||
pixel_max: dimensions.pixel_width as f32,
|
||||
pixel_cell: render_metrics.cell_size.width as f32,
|
||||
})
|
||||
.ceil() as usize,
|
||||
);
|
||||
border.top += ULength::new(
|
||||
config
|
||||
.window_frame
|
||||
.border_top_height
|
||||
.evaluate_as_pixels(DimensionContext {
|
||||
dpi: dimensions.dpi as f32,
|
||||
pixel_max: dimensions.pixel_height as f32,
|
||||
pixel_cell: render_metrics.cell_size.height as f32,
|
||||
})
|
||||
.ceil() as usize,
|
||||
);
|
||||
border.bottom += ULength::new(
|
||||
config
|
||||
.window_frame
|
||||
.border_bottom_height
|
||||
.evaluate_as_pixels(DimensionContext {
|
||||
dpi: dimensions.dpi as f32,
|
||||
pixel_max: dimensions.pixel_height as f32,
|
||||
pixel_cell: render_metrics.cell_size.height as f32,
|
||||
})
|
||||
.ceil() as usize,
|
||||
);
|
||||
|
||||
border
|
||||
}
|
||||
|
||||
pub fn get_os_border(&self) -> window::parameters::Border {
|
||||
Self::get_os_border_impl(
|
||||
&self.os_parameters,
|
||||
&self.config,
|
||||
&self.dimensions,
|
||||
&self.render_metrics,
|
||||
)
|
||||
}
|
||||
}
|
37
wezterm-gui/src/termwindow/render/corners.rs
Normal file
37
wezterm-gui/src/termwindow/render/corners.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use crate::customglyph::*;
|
||||
|
||||
pub const TOP_LEFT_ROUNDED_CORNER: &[Poly] = &[Poly {
|
||||
path: &[PolyCommand::Oval {
|
||||
center: (BlockCoord::One, BlockCoord::One),
|
||||
radiuses: (BlockCoord::One, BlockCoord::One),
|
||||
}],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Fill,
|
||||
}];
|
||||
|
||||
pub const BOTTOM_LEFT_ROUNDED_CORNER: &[Poly] = &[Poly {
|
||||
path: &[PolyCommand::Oval {
|
||||
center: (BlockCoord::One, BlockCoord::Zero),
|
||||
radiuses: (BlockCoord::One, BlockCoord::One),
|
||||
}],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Fill,
|
||||
}];
|
||||
|
||||
pub const TOP_RIGHT_ROUNDED_CORNER: &[Poly] = &[Poly {
|
||||
path: &[PolyCommand::Oval {
|
||||
center: (BlockCoord::Zero, BlockCoord::One),
|
||||
radiuses: (BlockCoord::One, BlockCoord::One),
|
||||
}],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Fill,
|
||||
}];
|
||||
|
||||
pub const BOTTOM_RIGHT_ROUNDED_CORNER: &[Poly] = &[Poly {
|
||||
path: &[PolyCommand::Oval {
|
||||
center: (BlockCoord::Zero, BlockCoord::Zero),
|
||||
radiuses: (BlockCoord::One, BlockCoord::One),
|
||||
}],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Fill,
|
||||
}];
|
274
wezterm-gui/src/termwindow/render/draw.rs
Normal file
274
wezterm-gui/src/termwindow/render/draw.rs
Normal file
@ -0,0 +1,274 @@
|
||||
use crate::colorease::ColorEaseUniform;
|
||||
use crate::termwindow::webgpu::ShaderUniform;
|
||||
use crate::termwindow::RenderFrame;
|
||||
use crate::uniforms::UniformBuilder;
|
||||
use ::window::glium;
|
||||
use ::window::glium::uniforms::{
|
||||
MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerWrapFunction,
|
||||
};
|
||||
use ::window::glium::{BlendingFunction, LinearBlendingFactor, Surface};
|
||||
use config::FreeTypeLoadTarget;
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn call_draw(&mut self, frame: &mut RenderFrame) -> anyhow::Result<()> {
|
||||
match frame {
|
||||
RenderFrame::Glium(ref mut frame) => self.call_draw_glium(frame),
|
||||
RenderFrame::WebGpu => self.call_draw_webgpu(),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_draw_webgpu(&mut self) -> anyhow::Result<()> {
|
||||
use crate::termwindow::webgpu::WebGpuTexture;
|
||||
|
||||
let webgpu = self.webgpu.as_mut().unwrap();
|
||||
let render_state = self.render_state.as_ref().unwrap();
|
||||
|
||||
let output = webgpu.surface.get_current_texture()?;
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = webgpu
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
let tex = render_state.glyph_cache.borrow().atlas.texture();
|
||||
let tex = tex.downcast_ref::<WebGpuTexture>().unwrap();
|
||||
let texture_view = tex.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let texture_linear_bind_group =
|
||||
webgpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &webgpu.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&webgpu.texture_linear_sampler),
|
||||
},
|
||||
],
|
||||
label: Some("linear bind group"),
|
||||
});
|
||||
|
||||
let texture_nearest_bind_group =
|
||||
webgpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &webgpu.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&webgpu.texture_nearest_sampler),
|
||||
},
|
||||
],
|
||||
label: Some("nearest bind group"),
|
||||
});
|
||||
|
||||
let mut cleared = false;
|
||||
let foreground_text_hsb = self.config.foreground_text_hsb;
|
||||
let foreground_text_hsb = [
|
||||
foreground_text_hsb.hue,
|
||||
foreground_text_hsb.saturation,
|
||||
foreground_text_hsb.brightness,
|
||||
];
|
||||
|
||||
let milliseconds = self.created.elapsed().as_millis() as u32;
|
||||
let projection = euclid::Transform3D::<f32, f32, f32>::ortho(
|
||||
-(self.dimensions.pixel_width as f32) / 2.0,
|
||||
self.dimensions.pixel_width as f32 / 2.0,
|
||||
self.dimensions.pixel_height as f32 / 2.0,
|
||||
-(self.dimensions.pixel_height as f32) / 2.0,
|
||||
-1.0,
|
||||
1.0,
|
||||
)
|
||||
.to_arrays_transposed();
|
||||
|
||||
for layer in render_state.layers.borrow().iter() {
|
||||
for idx in 0..3 {
|
||||
let vb = &layer.vb.borrow()[idx];
|
||||
let (vertex_count, index_count) = vb.vertex_index_count();
|
||||
let vertex_buffer;
|
||||
let uniforms;
|
||||
if vertex_count > 0 {
|
||||
let mut vertices = vb.current_vb_mut();
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: if cleared {
|
||||
wgpu::LoadOp::Load
|
||||
} else {
|
||||
wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.,
|
||||
g: 0.,
|
||||
b: 0.,
|
||||
a: 0.,
|
||||
})
|
||||
},
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
cleared = true;
|
||||
|
||||
uniforms = webgpu.create_uniform(ShaderUniform {
|
||||
foreground_text_hsb,
|
||||
milliseconds,
|
||||
projection,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&webgpu.render_pipeline);
|
||||
render_pass.set_bind_group(0, &uniforms, &[]);
|
||||
render_pass.set_bind_group(1, &texture_linear_bind_group, &[]);
|
||||
render_pass.set_bind_group(2, &texture_nearest_bind_group, &[]);
|
||||
vertex_buffer = vertices.webgpu_mut().recreate();
|
||||
vertex_buffer.unmap();
|
||||
render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
|
||||
render_pass
|
||||
.set_index_buffer(vb.indices.webgpu().slice(..), wgpu::IndexFormat::Uint32);
|
||||
render_pass.draw_indexed(0..index_count as _, 0, 0..1);
|
||||
}
|
||||
|
||||
vb.next_index();
|
||||
}
|
||||
}
|
||||
|
||||
// submit will accept anything that implements IntoIter
|
||||
webgpu.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn call_draw_glium(&mut self, frame: &mut glium::Frame) -> anyhow::Result<()> {
|
||||
use window::glium::texture::SrgbTexture2d;
|
||||
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let tex = gl_state.glyph_cache.borrow().atlas.texture();
|
||||
let tex = tex.downcast_ref::<SrgbTexture2d>().unwrap();
|
||||
|
||||
frame.clear_color(0., 0., 0., 0.);
|
||||
|
||||
let projection = euclid::Transform3D::<f32, f32, f32>::ortho(
|
||||
-(self.dimensions.pixel_width as f32) / 2.0,
|
||||
self.dimensions.pixel_width as f32 / 2.0,
|
||||
self.dimensions.pixel_height as f32 / 2.0,
|
||||
-(self.dimensions.pixel_height as f32) / 2.0,
|
||||
-1.0,
|
||||
1.0,
|
||||
)
|
||||
.to_arrays_transposed();
|
||||
|
||||
let use_subpixel = match self
|
||||
.config
|
||||
.freetype_render_target
|
||||
.unwrap_or(self.config.freetype_load_target)
|
||||
{
|
||||
FreeTypeLoadTarget::HorizontalLcd | FreeTypeLoadTarget::VerticalLcd => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let dual_source_blending = glium::DrawParameters {
|
||||
blend: glium::Blend {
|
||||
color: BlendingFunction::Addition {
|
||||
source: LinearBlendingFactor::SourceOneColor,
|
||||
destination: LinearBlendingFactor::OneMinusSourceOneColor,
|
||||
},
|
||||
alpha: BlendingFunction::Addition {
|
||||
source: LinearBlendingFactor::SourceOneColor,
|
||||
destination: LinearBlendingFactor::OneMinusSourceOneColor,
|
||||
},
|
||||
constant_value: (0.0, 0.0, 0.0, 0.0),
|
||||
},
|
||||
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let alpha_blending = glium::DrawParameters {
|
||||
blend: glium::Blend {
|
||||
color: BlendingFunction::Addition {
|
||||
source: LinearBlendingFactor::SourceAlpha,
|
||||
destination: LinearBlendingFactor::OneMinusSourceAlpha,
|
||||
},
|
||||
alpha: BlendingFunction::Addition {
|
||||
source: LinearBlendingFactor::One,
|
||||
destination: LinearBlendingFactor::OneMinusSourceAlpha,
|
||||
},
|
||||
constant_value: (0.0, 0.0, 0.0, 0.0),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Clamp and use the nearest texel rather than interpolate.
|
||||
// This prevents things like the box cursor outlines from
|
||||
// being randomly doubled in width or height
|
||||
let atlas_nearest_sampler = Sampler::new(&*tex)
|
||||
.wrap_function(SamplerWrapFunction::Clamp)
|
||||
.magnify_filter(MagnifySamplerFilter::Nearest)
|
||||
.minify_filter(MinifySamplerFilter::Nearest);
|
||||
|
||||
let atlas_linear_sampler = Sampler::new(&*tex)
|
||||
.wrap_function(SamplerWrapFunction::Clamp)
|
||||
.magnify_filter(MagnifySamplerFilter::Linear)
|
||||
.minify_filter(MinifySamplerFilter::Linear);
|
||||
|
||||
let foreground_text_hsb = self.config.foreground_text_hsb;
|
||||
let foreground_text_hsb = (
|
||||
foreground_text_hsb.hue,
|
||||
foreground_text_hsb.saturation,
|
||||
foreground_text_hsb.brightness,
|
||||
);
|
||||
|
||||
let milliseconds = self.created.elapsed().as_millis() as u32;
|
||||
|
||||
let cursor_blink: ColorEaseUniform = (*self.cursor_blink_state.borrow()).into();
|
||||
let blink: ColorEaseUniform = (*self.blink_state.borrow()).into();
|
||||
let rapid_blink: ColorEaseUniform = (*self.rapid_blink_state.borrow()).into();
|
||||
|
||||
for layer in gl_state.layers.borrow().iter() {
|
||||
for idx in 0..3 {
|
||||
let vb = &layer.vb.borrow()[idx];
|
||||
let (vertex_count, index_count) = vb.vertex_index_count();
|
||||
if vertex_count > 0 {
|
||||
let vertices = vb.current_vb_mut();
|
||||
let subpixel_aa = use_subpixel && idx == 1;
|
||||
|
||||
let mut uniforms = UniformBuilder::default();
|
||||
|
||||
uniforms.add("projection", &projection);
|
||||
uniforms.add("atlas_nearest_sampler", &atlas_nearest_sampler);
|
||||
uniforms.add("atlas_linear_sampler", &atlas_linear_sampler);
|
||||
uniforms.add("foreground_text_hsb", &foreground_text_hsb);
|
||||
uniforms.add("subpixel_aa", &subpixel_aa);
|
||||
uniforms.add("milliseconds", &milliseconds);
|
||||
uniforms.add_struct("cursor_blink", &cursor_blink);
|
||||
uniforms.add_struct("blink", &blink);
|
||||
uniforms.add_struct("rapid_blink", &rapid_blink);
|
||||
|
||||
frame.draw(
|
||||
vertices.glium().slice(0..vertex_count).unwrap(),
|
||||
vb.indices.glium().slice(0..index_count).unwrap(),
|
||||
gl_state.glyph_prog.as_ref().unwrap(),
|
||||
&uniforms,
|
||||
if subpixel_aa {
|
||||
&dual_source_blending
|
||||
} else {
|
||||
&alpha_blending
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
vb.next_index();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
521
wezterm-gui/src/termwindow/render/fancy_tab_bar.rs
Normal file
521
wezterm-gui/src/termwindow/render/fancy_tab_bar.rs
Normal file
@ -0,0 +1,521 @@
|
||||
use crate::customglyph::*;
|
||||
use crate::tabbar::{TabBarItem, TabEntry};
|
||||
use crate::termwindow::box_model::*;
|
||||
use crate::termwindow::render::corners::*;
|
||||
use crate::termwindow::render::rgbcolor_to_window_color;
|
||||
use crate::termwindow::render::window_buttons::window_button_element;
|
||||
use crate::termwindow::{UIItem, UIItemType};
|
||||
use crate::utilsprites::RenderMetrics;
|
||||
use config::{Dimension, DimensionContext, TabBarColors};
|
||||
use wezterm_term::color::{ColorAttribute, ColorPalette};
|
||||
use window::{IntegratedTitleButtonAlignment, IntegratedTitleButtonStyle};
|
||||
|
||||
const X_BUTTON: &[Poly] = &[
|
||||
Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::One, BlockCoord::Zero),
|
||||
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::One),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
},
|
||||
Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Zero),
|
||||
PolyCommand::LineTo(BlockCoord::One, BlockCoord::One),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
},
|
||||
];
|
||||
|
||||
const PLUS_BUTTON: &[Poly] = &[
|
||||
Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Frac(1, 2), BlockCoord::Zero),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 2), BlockCoord::One),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
},
|
||||
Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Frac(1, 2)),
|
||||
PolyCommand::LineTo(BlockCoord::One, BlockCoord::Frac(1, 2)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
},
|
||||
];
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn invalidate_fancy_tab_bar(&mut self) {
|
||||
self.fancy_tab_bar.take();
|
||||
}
|
||||
|
||||
pub fn build_fancy_tab_bar(&self, palette: &ColorPalette) -> anyhow::Result<ComputedElement> {
|
||||
let tab_bar_height = self.tab_bar_pixel_height()?;
|
||||
let font = self.fonts.title_font()?;
|
||||
let metrics = RenderMetrics::with_font_metrics(&font.metrics());
|
||||
let items = self.tab_bar.items();
|
||||
let colors = self
|
||||
.config
|
||||
.colors
|
||||
.as_ref()
|
||||
.and_then(|c| c.tab_bar.as_ref())
|
||||
.cloned()
|
||||
.unwrap_or_else(TabBarColors::default);
|
||||
|
||||
let mut left_status = vec![];
|
||||
let mut left_eles = vec![];
|
||||
let mut right_eles = vec![];
|
||||
let bar_colors = ElementColors {
|
||||
border: BorderColor::default(),
|
||||
bg: rgbcolor_to_window_color(if self.focused.is_some() {
|
||||
self.config.window_frame.active_titlebar_bg
|
||||
} else {
|
||||
self.config.window_frame.inactive_titlebar_bg
|
||||
})
|
||||
.into(),
|
||||
text: rgbcolor_to_window_color(if self.focused.is_some() {
|
||||
self.config.window_frame.active_titlebar_fg
|
||||
} else {
|
||||
self.config.window_frame.inactive_titlebar_fg
|
||||
})
|
||||
.into(),
|
||||
};
|
||||
|
||||
let item_to_elem = |item: &TabEntry| -> Element {
|
||||
let element = Element::with_line(&font, &item.title, palette);
|
||||
|
||||
let bg_color = item
|
||||
.title
|
||||
.get_cell(0)
|
||||
.and_then(|c| match c.attrs().background() {
|
||||
ColorAttribute::Default => None,
|
||||
col => Some(palette.resolve_bg(col)),
|
||||
});
|
||||
let fg_color = item
|
||||
.title
|
||||
.get_cell(0)
|
||||
.and_then(|c| match c.attrs().foreground() {
|
||||
ColorAttribute::Default => None,
|
||||
col => Some(palette.resolve_fg(col)),
|
||||
});
|
||||
|
||||
let new_tab = colors.new_tab();
|
||||
let new_tab_hover = colors.new_tab_hover();
|
||||
let active_tab = colors.active_tab();
|
||||
|
||||
match item.item {
|
||||
TabBarItem::RightStatus | TabBarItem::LeftStatus | TabBarItem::None => element
|
||||
.item_type(UIItemType::TabBar(TabBarItem::None))
|
||||
.line_height(Some(1.75))
|
||||
.margin(BoxDimension {
|
||||
left: Dimension::Cells(0.),
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.0),
|
||||
bottom: Dimension::Cells(0.),
|
||||
})
|
||||
.padding(BoxDimension {
|
||||
left: Dimension::Cells(0.5),
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.),
|
||||
bottom: Dimension::Cells(0.),
|
||||
})
|
||||
.border(BoxDimension::new(Dimension::Pixels(0.)))
|
||||
.colors(bar_colors.clone()),
|
||||
TabBarItem::NewTabButton => Element::new(
|
||||
&font,
|
||||
ElementContent::Poly {
|
||||
line_width: metrics.underline_height.max(2),
|
||||
poly: SizedPoly {
|
||||
poly: PLUS_BUTTON,
|
||||
width: Dimension::Pixels(metrics.cell_size.height as f32 / 2.),
|
||||
height: Dimension::Pixels(metrics.cell_size.height as f32 / 2.),
|
||||
},
|
||||
},
|
||||
)
|
||||
.vertical_align(VerticalAlign::Middle)
|
||||
.item_type(UIItemType::TabBar(item.item.clone()))
|
||||
.margin(BoxDimension {
|
||||
left: Dimension::Cells(0.5),
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.2),
|
||||
bottom: Dimension::Cells(0.),
|
||||
})
|
||||
.padding(BoxDimension {
|
||||
left: Dimension::Cells(0.5),
|
||||
right: Dimension::Cells(0.5),
|
||||
top: Dimension::Cells(0.2),
|
||||
bottom: Dimension::Cells(0.25),
|
||||
})
|
||||
.border(BoxDimension::new(Dimension::Pixels(1.)))
|
||||
.colors(ElementColors {
|
||||
border: BorderColor::default(),
|
||||
bg: new_tab.bg_color.to_linear().into(),
|
||||
text: new_tab.fg_color.to_linear().into(),
|
||||
})
|
||||
.hover_colors(Some(ElementColors {
|
||||
border: BorderColor::default(),
|
||||
bg: new_tab_hover.bg_color.to_linear().into(),
|
||||
text: new_tab_hover.fg_color.to_linear().into(),
|
||||
})),
|
||||
TabBarItem::Tab { active, .. } if active => element
|
||||
.vertical_align(VerticalAlign::Bottom)
|
||||
.item_type(UIItemType::TabBar(item.item.clone()))
|
||||
.margin(BoxDimension {
|
||||
left: Dimension::Cells(0.),
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.2),
|
||||
bottom: Dimension::Cells(0.),
|
||||
})
|
||||
.padding(BoxDimension {
|
||||
left: Dimension::Cells(0.5),
|
||||
right: Dimension::Cells(0.5),
|
||||
top: Dimension::Cells(0.2),
|
||||
bottom: Dimension::Cells(0.25),
|
||||
})
|
||||
.border(BoxDimension::new(Dimension::Pixels(1.)))
|
||||
.border_corners(Some(Corners {
|
||||
top_left: SizedPoly {
|
||||
width: Dimension::Cells(0.5),
|
||||
height: Dimension::Cells(0.5),
|
||||
poly: TOP_LEFT_ROUNDED_CORNER,
|
||||
},
|
||||
top_right: SizedPoly {
|
||||
width: Dimension::Cells(0.5),
|
||||
height: Dimension::Cells(0.5),
|
||||
poly: TOP_RIGHT_ROUNDED_CORNER,
|
||||
},
|
||||
bottom_left: SizedPoly::none(),
|
||||
bottom_right: SizedPoly::none(),
|
||||
}))
|
||||
.colors(ElementColors {
|
||||
border: BorderColor::new(
|
||||
bg_color
|
||||
.unwrap_or_else(|| active_tab.bg_color.into())
|
||||
.to_linear(),
|
||||
),
|
||||
bg: bg_color
|
||||
.unwrap_or_else(|| active_tab.bg_color.into())
|
||||
.to_linear()
|
||||
.into(),
|
||||
text: fg_color
|
||||
.unwrap_or_else(|| active_tab.fg_color.into())
|
||||
.to_linear()
|
||||
.into(),
|
||||
}),
|
||||
TabBarItem::Tab { .. } => element
|
||||
.vertical_align(VerticalAlign::Bottom)
|
||||
.item_type(UIItemType::TabBar(item.item.clone()))
|
||||
.margin(BoxDimension {
|
||||
left: Dimension::Cells(0.),
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.2),
|
||||
bottom: Dimension::Cells(0.),
|
||||
})
|
||||
.padding(BoxDimension {
|
||||
left: Dimension::Cells(0.5),
|
||||
right: Dimension::Cells(0.5),
|
||||
top: Dimension::Cells(0.2),
|
||||
bottom: Dimension::Cells(0.25),
|
||||
})
|
||||
.border(BoxDimension::new(Dimension::Pixels(1.)))
|
||||
.border_corners(Some(Corners {
|
||||
top_left: SizedPoly {
|
||||
width: Dimension::Cells(0.5),
|
||||
height: Dimension::Cells(0.5),
|
||||
poly: TOP_LEFT_ROUNDED_CORNER,
|
||||
},
|
||||
top_right: SizedPoly {
|
||||
width: Dimension::Cells(0.5),
|
||||
height: Dimension::Cells(0.5),
|
||||
poly: TOP_RIGHT_ROUNDED_CORNER,
|
||||
},
|
||||
bottom_left: SizedPoly {
|
||||
width: Dimension::Cells(0.),
|
||||
height: Dimension::Cells(0.33),
|
||||
poly: &[],
|
||||
},
|
||||
bottom_right: SizedPoly {
|
||||
width: Dimension::Cells(0.),
|
||||
height: Dimension::Cells(0.33),
|
||||
poly: &[],
|
||||
},
|
||||
}))
|
||||
.colors({
|
||||
let inactive_tab = colors.inactive_tab();
|
||||
let bg = bg_color
|
||||
.unwrap_or_else(|| inactive_tab.bg_color.into())
|
||||
.to_linear();
|
||||
let edge = colors.inactive_tab_edge().to_linear();
|
||||
ElementColors {
|
||||
border: BorderColor {
|
||||
left: bg,
|
||||
right: edge,
|
||||
top: bg,
|
||||
bottom: bg,
|
||||
},
|
||||
bg: bg.into(),
|
||||
text: fg_color
|
||||
.unwrap_or_else(|| inactive_tab.fg_color.into())
|
||||
.to_linear()
|
||||
.into(),
|
||||
}
|
||||
})
|
||||
.hover_colors({
|
||||
let inactive_tab_hover = colors.inactive_tab_hover();
|
||||
Some(ElementColors {
|
||||
border: BorderColor::new(
|
||||
bg_color
|
||||
.unwrap_or_else(|| inactive_tab_hover.bg_color.into())
|
||||
.to_linear(),
|
||||
),
|
||||
bg: bg_color
|
||||
.unwrap_or_else(|| inactive_tab_hover.bg_color.into())
|
||||
.to_linear()
|
||||
.into(),
|
||||
text: fg_color
|
||||
.unwrap_or_else(|| inactive_tab_hover.fg_color.into())
|
||||
.to_linear()
|
||||
.into(),
|
||||
})
|
||||
}),
|
||||
TabBarItem::WindowButton(button) => window_button_element(
|
||||
button,
|
||||
self.window_state.contains(window::WindowState::MAXIMIZED),
|
||||
&font,
|
||||
&metrics,
|
||||
&self.config,
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
let num_tabs: f32 = items
|
||||
.iter()
|
||||
.map(|item| match item.item {
|
||||
TabBarItem::NewTabButton | TabBarItem::Tab { .. } => 1.,
|
||||
_ => 0.,
|
||||
})
|
||||
.sum();
|
||||
let max_tab_width = ((self.dimensions.pixel_width as f32 / num_tabs)
|
||||
- (1.5 * metrics.cell_size.width as f32))
|
||||
.max(0.);
|
||||
|
||||
// Reserve space for the native titlebar buttons
|
||||
if self
|
||||
.config
|
||||
.window_decorations
|
||||
.contains(::window::WindowDecorations::INTEGRATED_BUTTONS)
|
||||
&& self.config.integrated_title_button_style == IntegratedTitleButtonStyle::MacOsNative
|
||||
{
|
||||
left_status.push(
|
||||
Element::new(&font, ElementContent::Text("".to_string())).margin(BoxDimension {
|
||||
left: Dimension::Cells(4.0), // FIXME: determine exact width of macos ... buttons
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.),
|
||||
bottom: Dimension::Cells(0.),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for item in items {
|
||||
match item.item {
|
||||
TabBarItem::LeftStatus => left_status.push(item_to_elem(item)),
|
||||
TabBarItem::None | TabBarItem::RightStatus => right_eles.push(item_to_elem(item)),
|
||||
TabBarItem::WindowButton(_) => {
|
||||
if self.config.integrated_title_button_alignment
|
||||
== IntegratedTitleButtonAlignment::Left
|
||||
{
|
||||
left_eles.push(item_to_elem(item))
|
||||
} else {
|
||||
right_eles.push(item_to_elem(item))
|
||||
}
|
||||
}
|
||||
TabBarItem::Tab { tab_idx, active } => {
|
||||
let mut elem = item_to_elem(item);
|
||||
elem.max_width = Some(Dimension::Pixels(max_tab_width));
|
||||
elem.content = match elem.content {
|
||||
ElementContent::Text(_) => unreachable!(),
|
||||
ElementContent::Poly { .. } => unreachable!(),
|
||||
ElementContent::Children(mut kids) => {
|
||||
let x_button = Element::new(
|
||||
&font,
|
||||
ElementContent::Poly {
|
||||
line_width: metrics.underline_height.max(2),
|
||||
poly: SizedPoly {
|
||||
poly: X_BUTTON,
|
||||
width: Dimension::Pixels(
|
||||
metrics.cell_size.height as f32 / 2.,
|
||||
),
|
||||
height: Dimension::Pixels(
|
||||
metrics.cell_size.height as f32 / 2.,
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
// Ensure that we draw our background over the
|
||||
// top of the rest of the tab contents
|
||||
.zindex(1)
|
||||
.vertical_align(VerticalAlign::Middle)
|
||||
.float(Float::Right)
|
||||
.item_type(UIItemType::CloseTab(tab_idx))
|
||||
.hover_colors({
|
||||
let inactive_tab_hover = colors.inactive_tab_hover();
|
||||
let active_tab = colors.active_tab();
|
||||
|
||||
Some(ElementColors {
|
||||
border: BorderColor::default(),
|
||||
bg: (if active {
|
||||
inactive_tab_hover.bg_color
|
||||
} else {
|
||||
active_tab.bg_color
|
||||
})
|
||||
.to_linear()
|
||||
.into(),
|
||||
text: (if active {
|
||||
inactive_tab_hover.fg_color
|
||||
} else {
|
||||
active_tab.fg_color
|
||||
})
|
||||
.to_linear()
|
||||
.into(),
|
||||
})
|
||||
})
|
||||
.padding(BoxDimension {
|
||||
left: Dimension::Cells(0.25),
|
||||
right: Dimension::Cells(0.25),
|
||||
top: Dimension::Cells(0.25),
|
||||
bottom: Dimension::Cells(0.25),
|
||||
})
|
||||
.margin(BoxDimension {
|
||||
left: Dimension::Cells(0.5),
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.),
|
||||
bottom: Dimension::Cells(0.),
|
||||
});
|
||||
|
||||
kids.push(x_button);
|
||||
ElementContent::Children(kids)
|
||||
}
|
||||
};
|
||||
left_eles.push(elem);
|
||||
}
|
||||
_ => left_eles.push(item_to_elem(item)),
|
||||
}
|
||||
}
|
||||
|
||||
let mut children = vec![];
|
||||
|
||||
if !left_status.is_empty() {
|
||||
children.push(
|
||||
Element::new(&font, ElementContent::Children(left_status))
|
||||
.colors(bar_colors.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
let window_buttons_at_left = self
|
||||
.config
|
||||
.window_decorations
|
||||
.contains(window::WindowDecorations::INTEGRATED_BUTTONS)
|
||||
&& (self.config.integrated_title_button_alignment
|
||||
== IntegratedTitleButtonAlignment::Left
|
||||
|| self.config.integrated_title_button_style
|
||||
== IntegratedTitleButtonStyle::MacOsNative);
|
||||
|
||||
let left_padding = if window_buttons_at_left {
|
||||
if self.config.integrated_title_button_style == IntegratedTitleButtonStyle::MacOsNative
|
||||
{
|
||||
if !self.window_state.contains(window::WindowState::FULL_SCREEN) {
|
||||
Dimension::Pixels(70.0)
|
||||
} else {
|
||||
Dimension::Cells(0.5)
|
||||
}
|
||||
} else {
|
||||
Dimension::Pixels(0.0)
|
||||
}
|
||||
} else {
|
||||
Dimension::Cells(0.5)
|
||||
};
|
||||
|
||||
children.push(
|
||||
Element::new(&font, ElementContent::Children(left_eles))
|
||||
.vertical_align(VerticalAlign::Bottom)
|
||||
.colors(bar_colors.clone())
|
||||
.padding(BoxDimension {
|
||||
left: left_padding,
|
||||
right: Dimension::Cells(0.),
|
||||
top: Dimension::Cells(0.),
|
||||
bottom: Dimension::Cells(0.),
|
||||
})
|
||||
.zindex(1),
|
||||
);
|
||||
children.push(
|
||||
Element::new(&font, ElementContent::Children(right_eles))
|
||||
.colors(bar_colors.clone())
|
||||
.float(Float::Right),
|
||||
);
|
||||
|
||||
let content = ElementContent::Children(children);
|
||||
|
||||
let tabs = Element::new(&font, content)
|
||||
.display(DisplayType::Block)
|
||||
.item_type(UIItemType::TabBar(TabBarItem::None))
|
||||
.min_width(Some(Dimension::Pixels(self.dimensions.pixel_width as f32)))
|
||||
.min_height(Some(Dimension::Pixels(tab_bar_height)))
|
||||
.vertical_align(VerticalAlign::Bottom)
|
||||
.colors(bar_colors);
|
||||
|
||||
let border = self.get_os_border();
|
||||
|
||||
let mut computed = self.compute_element(
|
||||
&LayoutContext {
|
||||
height: DimensionContext {
|
||||
dpi: self.dimensions.dpi as f32,
|
||||
pixel_max: self.dimensions.pixel_height as f32,
|
||||
pixel_cell: metrics.cell_size.height as f32,
|
||||
},
|
||||
width: DimensionContext {
|
||||
dpi: self.dimensions.dpi as f32,
|
||||
pixel_max: self.dimensions.pixel_width as f32,
|
||||
pixel_cell: metrics.cell_size.width as f32,
|
||||
},
|
||||
bounds: euclid::rect(
|
||||
border.left.get() as f32,
|
||||
0.,
|
||||
self.dimensions.pixel_width as f32 - (border.left + border.right).get() as f32,
|
||||
tab_bar_height,
|
||||
),
|
||||
metrics: &metrics,
|
||||
gl_state: self.render_state.as_ref().unwrap(),
|
||||
zindex: 10,
|
||||
},
|
||||
&tabs,
|
||||
)?;
|
||||
|
||||
computed.translate(euclid::vec2(
|
||||
0.,
|
||||
if self.config.tab_bar_at_bottom {
|
||||
self.dimensions.pixel_height as f32
|
||||
- (computed.bounds.height() + border.bottom.get() as f32)
|
||||
} else {
|
||||
border.top.get() as f32
|
||||
},
|
||||
));
|
||||
|
||||
Ok(computed)
|
||||
}
|
||||
|
||||
pub fn paint_fancy_tab_bar(&self) -> anyhow::Result<Vec<UIItem>> {
|
||||
let computed = self.fancy_tab_bar.as_ref().ok_or_else(|| {
|
||||
anyhow::anyhow!("paint_fancy_tab_bar called but fancy_tab_bar is None")
|
||||
})?;
|
||||
let ui_items = computed.ui_items();
|
||||
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
self.render_element(&computed, gl_state, None)?;
|
||||
|
||||
Ok(ui_items)
|
||||
}
|
||||
}
|
917
wezterm-gui/src/termwindow/render/mod.rs
Normal file
917
wezterm-gui/src/termwindow/render/mod.rs
Normal file
@ -0,0 +1,917 @@
|
||||
use crate::colorease::ColorEase;
|
||||
use crate::customglyph::{BlockKey, *};
|
||||
use crate::glyphcache::{CachedGlyph, GlyphCache};
|
||||
use crate::quad::{
|
||||
HeapQuadAllocator, QuadAllocator, QuadImpl, QuadTrait, TripleLayerQuadAllocator,
|
||||
TripleLayerQuadAllocatorTrait,
|
||||
};
|
||||
use crate::shapecache::*;
|
||||
use crate::termwindow::{BorrowedShapeCacheKey, RenderState, ShapedInfo, TermWindowNotif};
|
||||
use crate::utilsprites::RenderMetrics;
|
||||
use ::window::bitmaps::{TextureCoord, TextureRect, TextureSize};
|
||||
use ::window::{DeadKeyStatus, PointF, RectF, SizeF, WindowOps};
|
||||
use anyhow::anyhow;
|
||||
use config::{BoldBrightening, ConfigHandle, DimensionContext, TextStyle, VisualBellTarget};
|
||||
use euclid::num::Zero;
|
||||
use mux::pane::{Pane, PaneId};
|
||||
use mux::renderable::{RenderableDimensions, StableCursorPosition};
|
||||
use ordered_float::NotNan;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use termwiz::cellcluster::CellCluster;
|
||||
use termwiz::hyperlink::Hyperlink;
|
||||
use termwiz::surface::{CursorShape, CursorVisibility, SequenceNo};
|
||||
use wezterm_font::shaper::PresentationWidth;
|
||||
use wezterm_font::units::{IntPixelLength, PixelLength};
|
||||
use wezterm_font::{ClearShapeCache, GlyphInfo, LoadedFont};
|
||||
use wezterm_term::color::{ColorAttribute, ColorPalette, RgbColor};
|
||||
use wezterm_term::{CellAttributes, Line, StableRowIndex};
|
||||
use window::color::LinearRgba;
|
||||
|
||||
pub mod borders;
|
||||
pub mod corners;
|
||||
pub mod draw;
|
||||
pub mod fancy_tab_bar;
|
||||
pub mod paint;
|
||||
pub mod pane;
|
||||
pub mod screen_line;
|
||||
pub mod split;
|
||||
pub mod tab_bar;
|
||||
pub mod window_buttons;
|
||||
|
||||
/// The data that we associate with a line; we use this to cache it shape hash
|
||||
#[derive(Debug)]
|
||||
pub struct CachedLineState {
|
||||
pub id: u64,
|
||||
pub seqno: SequenceNo,
|
||||
pub shape_hash: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
||||
pub struct LineQuadCacheKey {
|
||||
pub config_generation: usize,
|
||||
pub shape_generation: usize,
|
||||
pub quad_generation: usize,
|
||||
/// Only set if cursor.y == stable_row
|
||||
pub composing: Option<String>,
|
||||
pub selection: Range<usize>,
|
||||
pub shape_hash: [u8; 16],
|
||||
pub top_pixel_y: NotNan<f32>,
|
||||
pub left_pixel_x: NotNan<f32>,
|
||||
pub phys_line_idx: usize,
|
||||
pub pane_id: PaneId,
|
||||
pub pane_is_active: bool,
|
||||
/// A cursor position with the y value fixed at 0.
|
||||
/// Only is_some() if the y value matches this row.
|
||||
pub cursor: Option<CursorProperties>,
|
||||
pub reverse_video: bool,
|
||||
pub password_input: bool,
|
||||
}
|
||||
|
||||
pub struct LineQuadCacheValue {
|
||||
/// For resolving hash collisions
|
||||
pub line: Line,
|
||||
pub expires: Option<Instant>,
|
||||
pub layers: HeapQuadAllocator,
|
||||
// Only set if the line contains any hyperlinks, so
|
||||
// that we can invalidate when it changes
|
||||
pub current_highlight: Option<Arc<Hyperlink>>,
|
||||
pub invalidate_on_hover_change: bool,
|
||||
}
|
||||
|
||||
pub struct LineToElementParams<'a> {
|
||||
pub line: &'a Line,
|
||||
pub config: &'a ConfigHandle,
|
||||
pub palette: &'a ColorPalette,
|
||||
pub stable_line_idx: StableRowIndex,
|
||||
pub window_is_transparent: bool,
|
||||
pub cursor: &'a StableCursorPosition,
|
||||
pub reverse_video: bool,
|
||||
pub shape_key: &'a Option<LineToEleShapeCacheKey>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub struct LineToEleShapeCacheKey {
|
||||
pub shape_hash: [u8; 16],
|
||||
pub composing: Option<(usize, String)>,
|
||||
pub shape_generation: usize,
|
||||
}
|
||||
|
||||
pub struct LineToElementShapeItem {
|
||||
pub expires: Option<Instant>,
|
||||
pub shaped: Rc<Vec<LineToElementShape>>,
|
||||
// Only set if the line contains any hyperlinks, so
|
||||
// that we can invalidate when it changes
|
||||
pub current_highlight: Option<Arc<Hyperlink>>,
|
||||
pub invalidate_on_hover_change: bool,
|
||||
}
|
||||
|
||||
pub struct LineToElementShape {
|
||||
pub attrs: CellAttributes,
|
||||
pub style: TextStyle,
|
||||
pub underline_tex_rect: TextureRect,
|
||||
pub fg_color: LinearRgba,
|
||||
pub bg_color: LinearRgba,
|
||||
pub underline_color: LinearRgba,
|
||||
pub x_pos: f32,
|
||||
pub pixel_width: f32,
|
||||
pub glyph_info: Rc<Vec<ShapedInfo>>,
|
||||
pub cluster: CellCluster,
|
||||
}
|
||||
|
||||
pub struct RenderScreenLineOpenGLResult {
|
||||
pub invalidate_on_hover_change: bool,
|
||||
}
|
||||
|
||||
pub struct RenderScreenLineOpenGLParams<'a> {
|
||||
/// zero-based offset from top of the window viewport to the line that
|
||||
/// needs to be rendered, measured in pixels
|
||||
pub top_pixel_y: f32,
|
||||
/// zero-based offset from left of the window viewport to the line that
|
||||
/// needs to be rendered, measured in pixels
|
||||
pub left_pixel_x: f32,
|
||||
pub pixel_width: f32,
|
||||
pub stable_line_idx: Option<StableRowIndex>,
|
||||
pub line: &'a Line,
|
||||
pub selection: Range<usize>,
|
||||
pub cursor: &'a StableCursorPosition,
|
||||
pub palette: &'a ColorPalette,
|
||||
pub dims: &'a RenderableDimensions,
|
||||
pub config: &'a ConfigHandle,
|
||||
pub pane: Option<&'a Arc<dyn Pane>>,
|
||||
|
||||
pub white_space: TextureRect,
|
||||
pub filled_box: TextureRect,
|
||||
|
||||
pub cursor_border_color: LinearRgba,
|
||||
pub foreground: LinearRgba,
|
||||
pub is_active: bool,
|
||||
|
||||
pub selection_fg: LinearRgba,
|
||||
pub selection_bg: LinearRgba,
|
||||
pub cursor_fg: LinearRgba,
|
||||
pub cursor_bg: LinearRgba,
|
||||
pub cursor_is_default_color: bool,
|
||||
|
||||
pub window_is_transparent: bool,
|
||||
pub default_bg: LinearRgba,
|
||||
|
||||
/// Override font resolution; useful together with
|
||||
/// the resolved title font
|
||||
pub font: Option<Rc<LoadedFont>>,
|
||||
pub style: Option<&'a TextStyle>,
|
||||
|
||||
/// If true, use the shaper-determined pixel positions,
|
||||
/// rather than using monospace cell based positions.
|
||||
pub use_pixel_positioning: bool,
|
||||
|
||||
pub render_metrics: RenderMetrics,
|
||||
pub shape_key: Option<LineToEleShapeCacheKey>,
|
||||
pub password_input: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub struct CursorProperties {
|
||||
pub position: StableCursorPosition,
|
||||
pub dead_key_or_leader: bool,
|
||||
pub cursor_is_default_color: bool,
|
||||
pub cursor_fg: LinearRgba,
|
||||
pub cursor_bg: LinearRgba,
|
||||
pub cursor_border_color: LinearRgba,
|
||||
}
|
||||
|
||||
pub struct ComputeCellFgBgParams<'a> {
|
||||
pub selected: bool,
|
||||
pub cursor: Option<&'a StableCursorPosition>,
|
||||
pub fg_color: LinearRgba,
|
||||
pub bg_color: LinearRgba,
|
||||
pub is_active_pane: bool,
|
||||
pub config: &'a ConfigHandle,
|
||||
pub selection_fg: LinearRgba,
|
||||
pub selection_bg: LinearRgba,
|
||||
pub cursor_fg: LinearRgba,
|
||||
pub cursor_bg: LinearRgba,
|
||||
pub cursor_is_default_color: bool,
|
||||
pub cursor_border_color: LinearRgba,
|
||||
pub pane: Option<&'a Arc<dyn Pane>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ComputeCellFgBgResult {
|
||||
pub fg_color: LinearRgba,
|
||||
pub fg_color_alt: LinearRgba,
|
||||
pub bg_color: LinearRgba,
|
||||
pub bg_color_alt: LinearRgba,
|
||||
pub fg_color_mix: f32,
|
||||
pub bg_color_mix: f32,
|
||||
pub cursor_border_color: LinearRgba,
|
||||
pub cursor_border_color_alt: LinearRgba,
|
||||
pub cursor_border_mix: f32,
|
||||
pub cursor_shape: Option<CursorShape>,
|
||||
}
|
||||
|
||||
/// Basic cache of computed data from prior cluster to avoid doing the same
|
||||
/// work for space separated clusters with the same style
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClusterStyleCache<'a> {
|
||||
attrs: &'a CellAttributes,
|
||||
style: &'a TextStyle,
|
||||
underline_tex_rect: TextureRect,
|
||||
fg_color: LinearRgba,
|
||||
bg_color: LinearRgba,
|
||||
underline_color: LinearRgba,
|
||||
}
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn update_next_frame_time(&self, next_due: Option<Instant>) {
|
||||
if next_due.is_some() {
|
||||
update_next_frame_time(&mut *self.has_animation.borrow_mut(), next_due);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_intensity_if_bell_target_ringing(
|
||||
&self,
|
||||
pane: &Arc<dyn Pane>,
|
||||
config: &ConfigHandle,
|
||||
target: VisualBellTarget,
|
||||
) -> Option<f32> {
|
||||
let mut per_pane = self.pane_state(pane.pane_id());
|
||||
if let Some(ringing) = per_pane.bell_start {
|
||||
if config.visual_bell.target == target {
|
||||
let mut color_ease = ColorEase::new(
|
||||
config.visual_bell.fade_in_duration_ms,
|
||||
config.visual_bell.fade_in_function,
|
||||
config.visual_bell.fade_out_duration_ms,
|
||||
config.visual_bell.fade_out_function,
|
||||
Some(ringing),
|
||||
);
|
||||
|
||||
let intensity = color_ease.intensity_one_shot();
|
||||
|
||||
match intensity {
|
||||
None => {
|
||||
per_pane.bell_start.take();
|
||||
}
|
||||
Some((intensity, next)) => {
|
||||
self.update_next_frame_time(Some(next));
|
||||
return Some(intensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn filled_rectangle<'a>(
|
||||
&self,
|
||||
layers: &'a mut TripleLayerQuadAllocator,
|
||||
layer_num: usize,
|
||||
rect: RectF,
|
||||
color: LinearRgba,
|
||||
) -> anyhow::Result<QuadImpl<'a>> {
|
||||
let mut quad = layers.allocate(layer_num)?;
|
||||
let left_offset = self.dimensions.pixel_width as f32 / 2.;
|
||||
let top_offset = self.dimensions.pixel_height as f32 / 2.;
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
quad.set_position(
|
||||
rect.min_x() as f32 - left_offset,
|
||||
rect.min_y() as f32 - top_offset,
|
||||
rect.max_x() as f32 - left_offset,
|
||||
rect.max_y() as f32 - top_offset,
|
||||
);
|
||||
quad.set_texture(gl_state.util_sprites.filled_box.texture_coords());
|
||||
quad.set_is_background();
|
||||
quad.set_fg_color(color);
|
||||
quad.set_hsv(None);
|
||||
Ok(quad)
|
||||
}
|
||||
|
||||
pub fn poly_quad<'a>(
|
||||
&self,
|
||||
layers: &'a mut TripleLayerQuadAllocator,
|
||||
layer_num: usize,
|
||||
point: PointF,
|
||||
polys: &'static [Poly],
|
||||
underline_height: IntPixelLength,
|
||||
cell_size: SizeF,
|
||||
color: LinearRgba,
|
||||
) -> anyhow::Result<QuadImpl<'a>> {
|
||||
let left_offset = self.dimensions.pixel_width as f32 / 2.;
|
||||
let top_offset = self.dimensions.pixel_height as f32 / 2.;
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let sprite = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_block(
|
||||
BlockKey::PolyWithCustomMetrics {
|
||||
polys,
|
||||
underline_height,
|
||||
cell_size: euclid::size2(cell_size.width as isize, cell_size.height as isize),
|
||||
},
|
||||
&self.render_metrics,
|
||||
)?
|
||||
.texture_coords();
|
||||
|
||||
let mut quad = layers.allocate(layer_num)?;
|
||||
|
||||
quad.set_position(
|
||||
point.x - left_offset,
|
||||
point.y - top_offset,
|
||||
(point.x + cell_size.width as f32) - left_offset,
|
||||
(point.y + cell_size.height as f32) - top_offset,
|
||||
);
|
||||
quad.set_texture(sprite);
|
||||
quad.set_fg_color(color);
|
||||
quad.set_hsv(None);
|
||||
quad.set_has_color(false);
|
||||
Ok(quad)
|
||||
}
|
||||
|
||||
pub fn min_scroll_bar_height(&self) -> f32 {
|
||||
self.config
|
||||
.min_scroll_bar_height
|
||||
.evaluate_as_pixels(DimensionContext {
|
||||
dpi: self.dimensions.dpi as f32,
|
||||
pixel_max: self.terminal_size.pixel_height as f32,
|
||||
pixel_cell: self.render_metrics.cell_size.height as f32,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn padding_left_top(&self) -> (f32, f32) {
|
||||
let h_context = DimensionContext {
|
||||
dpi: self.dimensions.dpi as f32,
|
||||
pixel_max: self.terminal_size.pixel_width as f32,
|
||||
pixel_cell: self.render_metrics.cell_size.width as f32,
|
||||
};
|
||||
let v_context = DimensionContext {
|
||||
dpi: self.dimensions.dpi as f32,
|
||||
pixel_max: self.terminal_size.pixel_height as f32,
|
||||
pixel_cell: self.render_metrics.cell_size.height as f32,
|
||||
};
|
||||
|
||||
let padding_left = self
|
||||
.config
|
||||
.window_padding
|
||||
.left
|
||||
.evaluate_as_pixels(h_context);
|
||||
let padding_top = self.config.window_padding.top.evaluate_as_pixels(v_context);
|
||||
|
||||
(padding_left, padding_top)
|
||||
}
|
||||
|
||||
fn resolve_lock_glyph(
|
||||
&self,
|
||||
style: &TextStyle,
|
||||
attrs: &CellAttributes,
|
||||
font: Option<&Rc<LoadedFont>>,
|
||||
gl_state: &RenderState,
|
||||
metrics: &RenderMetrics,
|
||||
) -> anyhow::Result<Rc<CachedGlyph>> {
|
||||
let fa_lock = "\u{f023}";
|
||||
let line = Line::from_text(fa_lock, attrs, 0, None);
|
||||
let cluster = line.cluster(None);
|
||||
let shape_info = self.cached_cluster_shape(style, &cluster[0], gl_state, font, metrics)?;
|
||||
Ok(Rc::clone(&shape_info[0].glyph))
|
||||
}
|
||||
|
||||
pub fn populate_block_quad(
|
||||
&self,
|
||||
block: BlockKey,
|
||||
gl_state: &RenderState,
|
||||
quads: &mut dyn QuadAllocator,
|
||||
pos_x: f32,
|
||||
params: &RenderScreenLineOpenGLParams,
|
||||
hsv: Option<config::HsbTransform>,
|
||||
glyph_color: LinearRgba,
|
||||
) -> anyhow::Result<()> {
|
||||
let sprite = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_block(block, ¶ms.render_metrics)?
|
||||
.texture_coords();
|
||||
|
||||
let mut quad = quads.allocate()?;
|
||||
let cell_width = params.render_metrics.cell_size.width as f32;
|
||||
let cell_height = params.render_metrics.cell_size.height as f32;
|
||||
let pos_y = (self.dimensions.pixel_height as f32 / -2.) + params.top_pixel_y;
|
||||
quad.set_position(pos_x, pos_y, pos_x + cell_width, pos_y + cell_height);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_fg_color(glyph_color);
|
||||
quad.set_texture(sprite);
|
||||
quad.set_has_color(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render iTerm2 style image attributes
|
||||
pub fn populate_image_quad(
|
||||
&self,
|
||||
image: &termwiz::image::ImageCell,
|
||||
gl_state: &RenderState,
|
||||
layers: &mut TripleLayerQuadAllocator,
|
||||
layer_num: usize,
|
||||
cell_idx: usize,
|
||||
params: &RenderScreenLineOpenGLParams,
|
||||
hsv: Option<config::HsbTransform>,
|
||||
glyph_color: LinearRgba,
|
||||
) -> anyhow::Result<()> {
|
||||
if !self.allow_images {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let padding = self
|
||||
.render_metrics
|
||||
.cell_size
|
||||
.height
|
||||
.max(params.render_metrics.cell_size.width) as usize;
|
||||
let padding = if padding.is_power_of_two() {
|
||||
padding
|
||||
} else {
|
||||
padding.next_power_of_two()
|
||||
};
|
||||
|
||||
let (sprite, next_due) = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_image(image.image_data(), Some(padding))?;
|
||||
self.update_next_frame_time(next_due);
|
||||
let width = sprite.coords.size.width;
|
||||
let height = sprite.coords.size.height;
|
||||
|
||||
let top_left = image.top_left();
|
||||
let bottom_right = image.bottom_right();
|
||||
|
||||
// We *could* call sprite.texture.to_texture_coords() here,
|
||||
// but since that takes integer pixel coordinates, we'd
|
||||
// lose precision and end up with visual artifacts.
|
||||
// Instead, we compute the texture coords here in floating point.
|
||||
|
||||
let texture_width = sprite.texture.width() as f32;
|
||||
let texture_height = sprite.texture.height() as f32;
|
||||
let origin = TextureCoord::new(
|
||||
(sprite.coords.origin.x as f32 + (*top_left.x * width as f32)) / texture_width,
|
||||
(sprite.coords.origin.y as f32 + (*top_left.y * height as f32)) / texture_height,
|
||||
);
|
||||
|
||||
let size = TextureSize::new(
|
||||
(*bottom_right.x - *top_left.x) * width as f32 / texture_width,
|
||||
(*bottom_right.y - *top_left.y) * height as f32 / texture_height,
|
||||
);
|
||||
|
||||
let texture_rect = TextureRect::new(origin, size);
|
||||
|
||||
let mut quad = layers.allocate(layer_num)?;
|
||||
let cell_width = params.render_metrics.cell_size.width as f32;
|
||||
let cell_height = params.render_metrics.cell_size.height as f32;
|
||||
let pos_y = (self.dimensions.pixel_height as f32 / -2.) + params.top_pixel_y;
|
||||
|
||||
let pos_x = (self.dimensions.pixel_width as f32 / -2.)
|
||||
+ params.left_pixel_x
|
||||
+ (cell_idx as f32 * cell_width);
|
||||
|
||||
let (padding_left, padding_top, padding_right, padding_bottom) = image.padding();
|
||||
|
||||
quad.set_position(
|
||||
pos_x + padding_left as f32,
|
||||
pos_y + padding_top as f32,
|
||||
pos_x + cell_width + padding_left as f32 - padding_right as f32,
|
||||
pos_y + cell_height + padding_top as f32 - padding_bottom as f32,
|
||||
);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_fg_color(glyph_color);
|
||||
quad.set_texture(texture_rect);
|
||||
quad.set_has_color(true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compute_cell_fg_bg(&self, params: ComputeCellFgBgParams) -> ComputeCellFgBgResult {
|
||||
if params.cursor.is_some() {
|
||||
if let Some(bg_color_mix) = self.get_intensity_if_bell_target_ringing(
|
||||
params.pane.expect("cursor only set if pane present"),
|
||||
params.config,
|
||||
VisualBellTarget::CursorColor,
|
||||
) {
|
||||
let (fg_color, bg_color) =
|
||||
if self.config.force_reverse_video_cursor && params.cursor_is_default_color {
|
||||
(params.bg_color, params.fg_color)
|
||||
} else {
|
||||
(params.cursor_fg, params.cursor_bg)
|
||||
};
|
||||
|
||||
// interpolate between the background color
|
||||
// and the the target color
|
||||
let bg_color_alt = params
|
||||
.config
|
||||
.resolved_palette
|
||||
.visual_bell
|
||||
.map(|c| c.to_linear())
|
||||
.unwrap_or(fg_color);
|
||||
|
||||
return ComputeCellFgBgResult {
|
||||
fg_color,
|
||||
fg_color_alt: fg_color,
|
||||
fg_color_mix: 0.,
|
||||
bg_color,
|
||||
bg_color_alt,
|
||||
bg_color_mix,
|
||||
cursor_shape: Some(CursorShape::Default),
|
||||
cursor_border_color: bg_color,
|
||||
cursor_border_color_alt: bg_color_alt,
|
||||
cursor_border_mix: bg_color_mix,
|
||||
};
|
||||
}
|
||||
|
||||
let dead_key_or_leader =
|
||||
self.dead_key_status != DeadKeyStatus::None || self.leader_is_active();
|
||||
|
||||
if dead_key_or_leader && params.is_active_pane {
|
||||
let (fg_color, bg_color) =
|
||||
if self.config.force_reverse_video_cursor && params.cursor_is_default_color {
|
||||
(params.bg_color, params.fg_color)
|
||||
} else {
|
||||
(params.cursor_fg, params.cursor_bg)
|
||||
};
|
||||
|
||||
let color = params
|
||||
.config
|
||||
.resolved_palette
|
||||
.compose_cursor
|
||||
.map(|c| c.to_linear())
|
||||
.unwrap_or(bg_color);
|
||||
|
||||
return ComputeCellFgBgResult {
|
||||
fg_color,
|
||||
fg_color_alt: fg_color,
|
||||
fg_color_mix: 0.,
|
||||
bg_color,
|
||||
bg_color_alt: bg_color,
|
||||
bg_color_mix: 0.,
|
||||
cursor_shape: Some(CursorShape::Default),
|
||||
cursor_border_color: color,
|
||||
cursor_border_color_alt: color,
|
||||
cursor_border_mix: 0.,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let (cursor_shape, visibility) = match params.cursor {
|
||||
Some(cursor) => (
|
||||
params
|
||||
.config
|
||||
.default_cursor_style
|
||||
.effective_shape(cursor.shape),
|
||||
cursor.visibility,
|
||||
),
|
||||
_ => (CursorShape::default(), CursorVisibility::Hidden),
|
||||
};
|
||||
|
||||
let focused_and_active = self.focused.is_some() && params.is_active_pane;
|
||||
|
||||
let (fg_color, bg_color, cursor_bg) = match (
|
||||
params.selected,
|
||||
focused_and_active,
|
||||
cursor_shape,
|
||||
visibility,
|
||||
) {
|
||||
// Selected text overrides colors
|
||||
(true, _, _, CursorVisibility::Hidden) => (
|
||||
params.selection_fg.when_fully_transparent(params.fg_color),
|
||||
params.selection_bg,
|
||||
params.cursor_bg,
|
||||
),
|
||||
// block Cursor cell overrides colors
|
||||
(
|
||||
_,
|
||||
true,
|
||||
CursorShape::BlinkingBlock | CursorShape::SteadyBlock,
|
||||
CursorVisibility::Visible,
|
||||
) => {
|
||||
if self.config.force_reverse_video_cursor && params.cursor_is_default_color {
|
||||
(params.bg_color, params.fg_color, params.fg_color)
|
||||
} else {
|
||||
(
|
||||
params.cursor_fg.when_fully_transparent(params.fg_color),
|
||||
params.cursor_bg,
|
||||
params.cursor_bg,
|
||||
)
|
||||
}
|
||||
}
|
||||
(
|
||||
_,
|
||||
true,
|
||||
CursorShape::BlinkingUnderline
|
||||
| CursorShape::SteadyUnderline
|
||||
| CursorShape::BlinkingBar
|
||||
| CursorShape::SteadyBar,
|
||||
CursorVisibility::Visible,
|
||||
) => {
|
||||
if self.config.force_reverse_video_cursor && params.cursor_is_default_color {
|
||||
(params.fg_color, params.bg_color, params.fg_color)
|
||||
} else {
|
||||
(params.fg_color, params.bg_color, params.cursor_bg)
|
||||
}
|
||||
}
|
||||
// Normally, render the cell as configured (or if the window is unfocused)
|
||||
_ => (params.fg_color, params.bg_color, params.cursor_border_color),
|
||||
};
|
||||
|
||||
let blinking = params.cursor.is_some()
|
||||
&& params.is_active_pane
|
||||
&& cursor_shape.is_blinking()
|
||||
&& params.config.cursor_blink_rate != 0
|
||||
&& self.focused.is_some();
|
||||
|
||||
let mut fg_color_alt = fg_color;
|
||||
let bg_color_alt = bg_color;
|
||||
let mut fg_color_mix = 0.;
|
||||
let bg_color_mix = 0.;
|
||||
let mut cursor_border_color_alt = cursor_bg;
|
||||
let mut cursor_border_mix = 0.;
|
||||
|
||||
if blinking {
|
||||
let mut color_ease = self.cursor_blink_state.borrow_mut();
|
||||
color_ease.update_start(self.prev_cursor.last_cursor_movement());
|
||||
let (intensity, next) = color_ease.intensity_continuous();
|
||||
|
||||
cursor_border_mix = intensity;
|
||||
cursor_border_color_alt = params.bg_color;
|
||||
|
||||
if matches!(
|
||||
cursor_shape,
|
||||
CursorShape::BlinkingBlock | CursorShape::SteadyBlock,
|
||||
) {
|
||||
fg_color_alt = params.fg_color;
|
||||
fg_color_mix = intensity;
|
||||
}
|
||||
|
||||
self.update_next_frame_time(Some(next));
|
||||
}
|
||||
|
||||
ComputeCellFgBgResult {
|
||||
fg_color,
|
||||
fg_color_alt,
|
||||
bg_color,
|
||||
bg_color_alt,
|
||||
fg_color_mix,
|
||||
bg_color_mix,
|
||||
cursor_border_color: cursor_bg,
|
||||
cursor_border_color_alt,
|
||||
cursor_border_mix,
|
||||
cursor_shape: if visibility == CursorVisibility::Visible {
|
||||
match cursor_shape {
|
||||
CursorShape::BlinkingBlock | CursorShape::SteadyBlock if focused_and_active => {
|
||||
Some(CursorShape::Default)
|
||||
}
|
||||
// When not focused, convert bar to block to make it more visually
|
||||
// distinct from the focused bar in another pane
|
||||
_shape if !focused_and_active => Some(CursorShape::SteadyBlock),
|
||||
shape => Some(shape),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn glyph_infos_to_glyphs(
|
||||
&self,
|
||||
style: &TextStyle,
|
||||
glyph_cache: &mut GlyphCache,
|
||||
infos: &[GlyphInfo],
|
||||
font: &Rc<LoadedFont>,
|
||||
metrics: &RenderMetrics,
|
||||
) -> anyhow::Result<Vec<Rc<CachedGlyph>>> {
|
||||
let mut glyphs = Vec::with_capacity(infos.len());
|
||||
let mut iter = infos.iter().peekable();
|
||||
while let Some(info) = iter.next() {
|
||||
if self.config.custom_block_glyphs {
|
||||
if info.only_char.and_then(BlockKey::from_char).is_some() {
|
||||
// Don't bother rendering the glyph from the font, as it can
|
||||
// have incorrect advance metrics.
|
||||
// Instead, just use our pixel-perfect cell metrics
|
||||
glyphs.push(Rc::new(CachedGlyph {
|
||||
brightness_adjust: 1.0,
|
||||
has_color: false,
|
||||
texture: None,
|
||||
x_advance: PixelLength::new(metrics.cell_size.width as f64),
|
||||
x_offset: PixelLength::zero(),
|
||||
y_offset: PixelLength::zero(),
|
||||
bearing_x: PixelLength::zero(),
|
||||
bearing_y: PixelLength::zero(),
|
||||
scale: 1.0,
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let followed_by_space = match iter.peek() {
|
||||
Some(next_info) => next_info.is_space,
|
||||
None => false,
|
||||
};
|
||||
|
||||
glyphs.push(glyph_cache.cached_glyph(
|
||||
info,
|
||||
&style,
|
||||
followed_by_space,
|
||||
font,
|
||||
metrics,
|
||||
info.num_cells,
|
||||
)?);
|
||||
}
|
||||
Ok(glyphs)
|
||||
}
|
||||
|
||||
/// Shape the printable text from a cluster
|
||||
fn cached_cluster_shape(
|
||||
&self,
|
||||
style: &TextStyle,
|
||||
cluster: &CellCluster,
|
||||
gl_state: &RenderState,
|
||||
font: Option<&Rc<LoadedFont>>,
|
||||
metrics: &RenderMetrics,
|
||||
) -> anyhow::Result<Rc<Vec<ShapedInfo>>> {
|
||||
let shape_resolve_start = Instant::now();
|
||||
let key = BorrowedShapeCacheKey {
|
||||
style,
|
||||
text: &cluster.text,
|
||||
};
|
||||
let glyph_info = match self.lookup_cached_shape(&key) {
|
||||
Some(Ok(info)) => info,
|
||||
Some(Err(err)) => return Err(err),
|
||||
None => {
|
||||
let font = match font {
|
||||
Some(f) => Rc::clone(f),
|
||||
None => self.fonts.resolve_font(style)?,
|
||||
};
|
||||
let window = self.window.as_ref().unwrap().clone();
|
||||
|
||||
let presentation_width = PresentationWidth::with_cluster(&cluster);
|
||||
|
||||
match font.shape(
|
||||
&cluster.text,
|
||||
move || window.notify(TermWindowNotif::InvalidateShapeCache),
|
||||
BlockKey::filter_out_synthetic,
|
||||
Some(cluster.presentation),
|
||||
cluster.direction,
|
||||
None, // FIXME: need more paragraph context
|
||||
Some(&presentation_width),
|
||||
) {
|
||||
Ok(info) => {
|
||||
let glyphs = self.glyph_infos_to_glyphs(
|
||||
&style,
|
||||
&mut gl_state.glyph_cache.borrow_mut(),
|
||||
&info,
|
||||
&font,
|
||||
metrics,
|
||||
)?;
|
||||
let shaped = Rc::new(ShapedInfo::process(&info, &glyphs));
|
||||
|
||||
self.shape_cache
|
||||
.borrow_mut()
|
||||
.put(key.to_owned(), Ok(Rc::clone(&shaped)));
|
||||
shaped
|
||||
}
|
||||
Err(err) => {
|
||||
if err.root_cause().downcast_ref::<ClearShapeCache>().is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let res = anyhow!("shaper error: {}", err);
|
||||
self.shape_cache.borrow_mut().put(key.to_owned(), Err(err));
|
||||
return Err(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
metrics::histogram!("cached_cluster_shape", shape_resolve_start.elapsed());
|
||||
log::trace!(
|
||||
"shape_resolve for cluster len {} -> elapsed {:?}",
|
||||
cluster.text.len(),
|
||||
shape_resolve_start.elapsed()
|
||||
);
|
||||
Ok(glyph_info)
|
||||
}
|
||||
|
||||
fn lookup_cached_shape(
|
||||
&self,
|
||||
key: &dyn ShapeCacheKeyTrait,
|
||||
) -> Option<anyhow::Result<Rc<Vec<ShapedInfo>>>> {
|
||||
match self.shape_cache.borrow_mut().get(key) {
|
||||
Some(Ok(info)) => Some(Ok(Rc::clone(info))),
|
||||
Some(Err(err)) => Some(Err(anyhow!("cached shaper error: {}", err))),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recreate_texture_atlas(&mut self, size: Option<usize>) -> anyhow::Result<()> {
|
||||
self.shape_generation += 1;
|
||||
self.shape_cache.borrow_mut().clear();
|
||||
self.line_to_ele_shape_cache.borrow_mut().clear();
|
||||
if let Some(render_state) = self.render_state.as_mut() {
|
||||
render_state.recreate_texture_atlas(&self.fonts, &self.render_metrics, size)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn shape_hash_for_line(&mut self, line: &Line) -> [u8; 16] {
|
||||
let seqno = line.current_seqno();
|
||||
let mut id = None;
|
||||
if let Some(cached_arc) = line.get_appdata() {
|
||||
if let Some(line_state) = cached_arc.downcast_ref::<CachedLineState>() {
|
||||
if line_state.seqno == seqno {
|
||||
// Touch the LRU
|
||||
self.line_state_cache.borrow_mut().get(&line_state.id);
|
||||
return line_state.shape_hash;
|
||||
}
|
||||
id.replace(line_state.id);
|
||||
}
|
||||
}
|
||||
|
||||
let id = id.unwrap_or_else(|| {
|
||||
let id = self.next_line_state_id;
|
||||
self.next_line_state_id += 1;
|
||||
id
|
||||
});
|
||||
|
||||
let shape_hash = line.compute_shape_hash();
|
||||
|
||||
let state = Arc::new(CachedLineState {
|
||||
id,
|
||||
seqno,
|
||||
shape_hash,
|
||||
});
|
||||
|
||||
line.set_appdata(Arc::clone(&state));
|
||||
|
||||
self.line_state_cache.borrow_mut().put(id, state);
|
||||
shape_hash
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgbcolor_to_window_color(color: RgbColor) -> LinearRgba {
|
||||
rgbcolor_alpha_to_window_color(color, 1.0)
|
||||
}
|
||||
|
||||
pub fn rgbcolor_alpha_to_window_color(color: RgbColor, alpha: f32) -> LinearRgba {
|
||||
let (red, green, blue, _) = color.to_linear_tuple_rgba().tuple();
|
||||
LinearRgba::with_components(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
fn resolve_fg_color_attr(
|
||||
attrs: &CellAttributes,
|
||||
fg: ColorAttribute,
|
||||
palette: &ColorPalette,
|
||||
config: &ConfigHandle,
|
||||
style: &config::TextStyle,
|
||||
) -> LinearRgba {
|
||||
match fg {
|
||||
wezterm_term::color::ColorAttribute::Default => {
|
||||
if let Some(fg) = style.foreground {
|
||||
fg.into()
|
||||
} else {
|
||||
palette.resolve_fg(attrs.foreground())
|
||||
}
|
||||
}
|
||||
wezterm_term::color::ColorAttribute::PaletteIndex(idx)
|
||||
if idx < 8 && config.bold_brightens_ansi_colors != BoldBrightening::No =>
|
||||
{
|
||||
// For compatibility purposes, switch to a brighter version
|
||||
// of one of the standard ANSI colors when Bold is enabled.
|
||||
// This lifts black to dark grey.
|
||||
let idx = if attrs.intensity() == wezterm_term::Intensity::Bold {
|
||||
idx + 8
|
||||
} else {
|
||||
idx
|
||||
};
|
||||
|
||||
palette.resolve_fg(wezterm_term::color::ColorAttribute::PaletteIndex(idx))
|
||||
}
|
||||
_ => palette.resolve_fg(fg),
|
||||
}
|
||||
.to_linear()
|
||||
}
|
||||
|
||||
fn update_next_frame_time(storage: &mut Option<Instant>, next_due: Option<Instant>) {
|
||||
if let Some(next_due) = next_due {
|
||||
match storage.take() {
|
||||
None => {
|
||||
storage.replace(next_due);
|
||||
}
|
||||
Some(t) if next_due < t => {
|
||||
storage.replace(next_due);
|
||||
}
|
||||
Some(t) => {
|
||||
storage.replace(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn same_hyperlink(a: Option<&Arc<Hyperlink>>, b: Option<&Arc<Hyperlink>>) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
247
wezterm-gui/src/termwindow/render/paint.rs
Normal file
247
wezterm-gui/src/termwindow/render/paint.rs
Normal file
@ -0,0 +1,247 @@
|
||||
use crate::termwindow::{RenderFrame, TermWindowNotif};
|
||||
use ::window::bitmaps::atlas::OutOfTextureSpace;
|
||||
use ::window::WindowOps;
|
||||
use smol::Timer;
|
||||
use std::time::{Duration, Instant};
|
||||
use wezterm_font::ClearShapeCache;
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn paint_impl(&mut self, frame: &mut RenderFrame) {
|
||||
self.num_frames += 1;
|
||||
// If nothing on screen needs animating, then we can avoid
|
||||
// invalidating as frequently
|
||||
*self.has_animation.borrow_mut() = None;
|
||||
// Start with the assumption that we should allow images to render
|
||||
self.allow_images = true;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
{
|
||||
let diff = start.duration_since(self.last_fps_check_time);
|
||||
if diff > Duration::from_secs(1) {
|
||||
let seconds = diff.as_secs_f32();
|
||||
self.fps = self.num_frames as f32 / seconds;
|
||||
self.num_frames = 0;
|
||||
self.last_fps_check_time = start;
|
||||
}
|
||||
}
|
||||
|
||||
'pass: for pass in 0.. {
|
||||
match self.paint_opengl_pass() {
|
||||
Ok(_) => match self.render_state.as_mut().unwrap().allocated_more_quads() {
|
||||
Ok(allocated) => {
|
||||
if !allocated {
|
||||
break 'pass;
|
||||
}
|
||||
self.invalidate_fancy_tab_bar();
|
||||
self.invalidate_modal();
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("{:#}", err);
|
||||
break 'pass;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
if let Some(&OutOfTextureSpace {
|
||||
size: Some(size),
|
||||
current_size,
|
||||
}) = err.root_cause().downcast_ref::<OutOfTextureSpace>()
|
||||
{
|
||||
let result = if pass == 0 {
|
||||
// Let's try clearing out the atlas and trying again
|
||||
// self.clear_texture_atlas()
|
||||
log::trace!("recreate_texture_atlas");
|
||||
self.recreate_texture_atlas(Some(current_size))
|
||||
} else {
|
||||
log::trace!("grow texture atlas to {}", size);
|
||||
self.recreate_texture_atlas(Some(size))
|
||||
};
|
||||
self.invalidate_fancy_tab_bar();
|
||||
self.invalidate_modal();
|
||||
|
||||
if let Err(err) = result {
|
||||
if self.allow_images {
|
||||
self.allow_images = false;
|
||||
log::info!(
|
||||
"Not enough texture space ({:#}); \
|
||||
will retry render with images disabled",
|
||||
err
|
||||
);
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to {} texture: {}",
|
||||
if pass == 0 { "clear" } else { "resize" },
|
||||
err
|
||||
);
|
||||
break 'pass;
|
||||
}
|
||||
}
|
||||
} else if err.root_cause().downcast_ref::<ClearShapeCache>().is_some() {
|
||||
self.invalidate_fancy_tab_bar();
|
||||
self.invalidate_modal();
|
||||
self.shape_generation += 1;
|
||||
self.shape_cache.borrow_mut().clear();
|
||||
self.line_to_ele_shape_cache.borrow_mut().clear();
|
||||
} else {
|
||||
log::error!("paint_opengl_pass failed: {:#}", err);
|
||||
break 'pass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("paint_impl before call_draw elapsed={:?}", start.elapsed());
|
||||
|
||||
self.call_draw(frame).ok();
|
||||
self.last_frame_duration = start.elapsed();
|
||||
log::debug!(
|
||||
"paint_impl elapsed={:?}, fps={}",
|
||||
self.last_frame_duration,
|
||||
self.fps
|
||||
);
|
||||
metrics::histogram!("gui.paint.opengl", self.last_frame_duration);
|
||||
metrics::histogram!("gui.paint.opengl.rate", 1.);
|
||||
self.update_title_post_status();
|
||||
|
||||
// If self.has_animation is some, then the last render detected
|
||||
// image attachments with multiple frames, so we also need to
|
||||
// invalidate the viewport when the next frame is due
|
||||
if self.focused.is_some() {
|
||||
if let Some(next_due) = *self.has_animation.borrow() {
|
||||
let prior = self.scheduled_animation.borrow_mut().take();
|
||||
match prior {
|
||||
Some(prior) if prior <= next_due => {
|
||||
// Already due before that time
|
||||
}
|
||||
_ => {
|
||||
self.scheduled_animation.borrow_mut().replace(next_due);
|
||||
let window = self.window.clone().take().unwrap();
|
||||
promise::spawn::spawn(async move {
|
||||
Timer::at(next_due).await;
|
||||
let win = window.clone();
|
||||
window.notify(TermWindowNotif::Apply(Box::new(move |tw| {
|
||||
tw.scheduled_animation.borrow_mut().take();
|
||||
win.invalidate();
|
||||
})));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint_modal(&mut self) -> anyhow::Result<()> {
|
||||
if let Some(modal) = self.get_modal() {
|
||||
for computed in modal.computed_element(self)?.iter() {
|
||||
let mut ui_items = computed.ui_items();
|
||||
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
self.render_element(&computed, gl_state, None)?;
|
||||
|
||||
self.ui_items.append(&mut ui_items);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn paint_opengl_pass(&mut self) -> anyhow::Result<()> {
|
||||
{
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
for layer in gl_state.layers.borrow().iter() {
|
||||
layer.clear_quad_allocation();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out UI item positions; we'll rebuild these as we render
|
||||
self.ui_items.clear();
|
||||
|
||||
let panes = self.get_panes_to_render();
|
||||
let num_panes = panes.len();
|
||||
let focused = self.focused.is_some();
|
||||
let window_is_transparent =
|
||||
!self.window_background.is_empty() || self.config.window_background_opacity != 1.0;
|
||||
|
||||
let start = Instant::now();
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let layer = gl_state.layer_for_zindex(0)?;
|
||||
let mut layers = layer.quad_allocator();
|
||||
log::trace!("quad map elapsed {:?}", start.elapsed());
|
||||
metrics::histogram!("quad.map", start.elapsed());
|
||||
|
||||
// Render the full window background
|
||||
match (self.window_background.is_empty(), self.allow_images) {
|
||||
(false, true) => {
|
||||
let bg_color = self.palette().background.to_linear();
|
||||
|
||||
let top = panes
|
||||
.iter()
|
||||
.find(|p| p.is_active)
|
||||
.map(|p| match self.get_viewport(p.pane.pane_id()) {
|
||||
Some(top) => top,
|
||||
None => p.pane.get_dimensions().physical_top,
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
self.render_backgrounds(bg_color, top)?;
|
||||
}
|
||||
_ if window_is_transparent && panes.len() > 1 => {
|
||||
// Avoid doubling up the background color: the panes
|
||||
// will render out through the padding so there
|
||||
// should be no gaps that need filling in
|
||||
}
|
||||
_ => {
|
||||
// Regular window background color
|
||||
let background = if panes.len() == 1 {
|
||||
// If we're the only pane, use the pane's palette
|
||||
// to draw the padding background
|
||||
panes[0].pane.palette().background
|
||||
} else {
|
||||
self.palette().background
|
||||
}
|
||||
.to_linear()
|
||||
.mul_alpha(self.config.window_background_opacity);
|
||||
|
||||
self.filled_rectangle(
|
||||
&mut layers,
|
||||
0,
|
||||
euclid::rect(
|
||||
0.,
|
||||
0.,
|
||||
self.dimensions.pixel_width as f32,
|
||||
self.dimensions.pixel_height as f32,
|
||||
),
|
||||
background,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for pos in panes {
|
||||
if pos.is_active {
|
||||
self.update_text_cursor(&pos);
|
||||
if focused {
|
||||
pos.pane.advise_focus();
|
||||
mux::Mux::get().record_focus_for_current_identity(pos.pane.pane_id());
|
||||
}
|
||||
}
|
||||
self.paint_pane_opengl(&pos, num_panes, &mut layers)?;
|
||||
}
|
||||
|
||||
if let Some(pane) = self.get_active_pane_or_overlay() {
|
||||
let splits = self.get_splits();
|
||||
for split in &splits {
|
||||
self.paint_split_opengl(&mut layers, split, &pane)?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_tab_bar {
|
||||
self.paint_tab_bar(&mut layers)?;
|
||||
}
|
||||
|
||||
self.paint_window_borders(&mut layers)?;
|
||||
drop(layers);
|
||||
self.paint_modal()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
685
wezterm-gui/src/termwindow/render/pane.rs
Normal file
685
wezterm-gui/src/termwindow/render/pane.rs
Normal file
@ -0,0 +1,685 @@
|
||||
use crate::quad::{HeapQuadAllocator, QuadTrait, TripleLayerQuadAllocator};
|
||||
use crate::selection::SelectionRange;
|
||||
use crate::termwindow::box_model::*;
|
||||
use crate::termwindow::render::{
|
||||
same_hyperlink, CursorProperties, LineQuadCacheKey, LineQuadCacheValue, LineToEleShapeCacheKey,
|
||||
RenderScreenLineOpenGLParams,
|
||||
};
|
||||
use crate::termwindow::{ScrollHit, UIItem, UIItemType};
|
||||
use ::window::bitmaps::TextureRect;
|
||||
use ::window::DeadKeyStatus;
|
||||
use config::VisualBellTarget;
|
||||
use mux::pane::{PaneId, WithPaneLines};
|
||||
use mux::renderable::{RenderableDimensions, StableCursorPosition};
|
||||
use mux::tab::PositionedPane;
|
||||
use ordered_float::NotNan;
|
||||
use std::time::Instant;
|
||||
use wezterm_dynamic::Value;
|
||||
use wezterm_term::color::{ColorAttribute, ColorPalette};
|
||||
use wezterm_term::{Line, StableRowIndex};
|
||||
use window::color::LinearRgba;
|
||||
|
||||
impl crate::TermWindow {
|
||||
fn paint_pane_opengl_new(
|
||||
&mut self,
|
||||
pos: &PositionedPane,
|
||||
num_panes: usize,
|
||||
) -> anyhow::Result<()> {
|
||||
let computed = self.build_pane(pos, num_panes)?;
|
||||
let mut ui_items = computed.ui_items();
|
||||
self.ui_items.append(&mut ui_items);
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
self.render_element(&computed, gl_state, None)
|
||||
}
|
||||
|
||||
pub fn paint_pane_opengl(
|
||||
&mut self,
|
||||
pos: &PositionedPane,
|
||||
num_panes: usize,
|
||||
layers: &mut TripleLayerQuadAllocator,
|
||||
) -> anyhow::Result<()> {
|
||||
if self.config.use_box_model_render {
|
||||
return self.paint_pane_opengl_new(pos, num_panes);
|
||||
}
|
||||
|
||||
self.check_for_dirty_lines_and_invalidate_selection(&pos.pane);
|
||||
/*
|
||||
let zone = {
|
||||
let dims = pos.pane.get_dimensions();
|
||||
let position = self
|
||||
.get_viewport(pos.pane.pane_id())
|
||||
.unwrap_or(dims.physical_top);
|
||||
|
||||
let zones = self.get_semantic_zones(&pos.pane);
|
||||
let idx = match zones.binary_search_by(|zone| zone.start_y.cmp(&position)) {
|
||||
Ok(idx) | Err(idx) => idx,
|
||||
};
|
||||
let idx = ((idx as isize) - 1).max(0) as usize;
|
||||
zones.get(idx).cloned()
|
||||
};
|
||||
*/
|
||||
|
||||
let global_cursor_fg = self.palette().cursor_fg;
|
||||
let global_cursor_bg = self.palette().cursor_bg;
|
||||
let config = self.config.clone();
|
||||
let palette = pos.pane.palette();
|
||||
|
||||
let (padding_left, padding_top) = self.padding_left_top();
|
||||
|
||||
let tab_bar_height = if self.show_tab_bar {
|
||||
self.tab_bar_pixel_height()?
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
let (top_bar_height, bottom_bar_height) = if self.config.tab_bar_at_bottom {
|
||||
(0.0, tab_bar_height)
|
||||
} else {
|
||||
(tab_bar_height, 0.0)
|
||||
};
|
||||
|
||||
let border = self.get_os_border();
|
||||
let top_pixel_y = top_bar_height + padding_top + border.top.get() as f32;
|
||||
|
||||
let cursor = pos.pane.get_cursor_position();
|
||||
if pos.is_active {
|
||||
self.prev_cursor.update(&cursor);
|
||||
}
|
||||
|
||||
let pane_id = pos.pane.pane_id();
|
||||
let current_viewport = self.get_viewport(pane_id);
|
||||
let dims = pos.pane.get_dimensions();
|
||||
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
|
||||
let cursor_border_color = palette.cursor_border.to_linear();
|
||||
let foreground = palette.foreground.to_linear();
|
||||
let white_space = gl_state.util_sprites.white_space.texture_coords();
|
||||
let filled_box = gl_state.util_sprites.filled_box.texture_coords();
|
||||
|
||||
let window_is_transparent =
|
||||
!self.window_background.is_empty() || config.window_background_opacity != 1.0;
|
||||
|
||||
let default_bg = palette
|
||||
.resolve_bg(ColorAttribute::Default)
|
||||
.to_linear()
|
||||
.mul_alpha(if window_is_transparent {
|
||||
0.
|
||||
} else {
|
||||
config.text_background_opacity
|
||||
});
|
||||
|
||||
let cell_width = self.render_metrics.cell_size.width as f32;
|
||||
let cell_height = self.render_metrics.cell_size.height as f32;
|
||||
let background_rect = {
|
||||
// We want to fill out to the edges of the splits
|
||||
let (x, width_delta) = if pos.left == 0 {
|
||||
(
|
||||
0.,
|
||||
padding_left + border.left.get() as f32 + (cell_width / 2.0),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
padding_left + border.left.get() as f32 - (cell_width / 2.0)
|
||||
+ (pos.left as f32 * cell_width),
|
||||
cell_width,
|
||||
)
|
||||
};
|
||||
|
||||
let (y, height_delta) = if pos.top == 0 {
|
||||
(
|
||||
(top_pixel_y - padding_top),
|
||||
padding_top + (cell_height / 2.0),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
top_pixel_y + (pos.top as f32 * cell_height) - (cell_height / 2.0),
|
||||
cell_height,
|
||||
)
|
||||
};
|
||||
euclid::rect(
|
||||
x,
|
||||
y,
|
||||
// Go all the way to the right edge if we're right-most
|
||||
if pos.left + pos.width >= self.terminal_size.cols as usize {
|
||||
self.dimensions.pixel_width as f32 - x
|
||||
} else {
|
||||
(pos.width as f32 * cell_width) + width_delta
|
||||
},
|
||||
// Go all the way to the bottom if we're bottom-most
|
||||
if pos.top + pos.height >= self.terminal_size.rows as usize {
|
||||
self.dimensions.pixel_height as f32 - y
|
||||
} else {
|
||||
(pos.height as f32 * cell_height) + height_delta as f32
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
if num_panes > 1 && self.window_background.is_empty() {
|
||||
// Per-pane, palette-specified background
|
||||
|
||||
let mut quad = self.filled_rectangle(
|
||||
layers,
|
||||
0,
|
||||
background_rect,
|
||||
palette
|
||||
.background
|
||||
.to_linear()
|
||||
.mul_alpha(config.window_background_opacity),
|
||||
)?;
|
||||
quad.set_hsv(if pos.is_active {
|
||||
None
|
||||
} else {
|
||||
Some(config.inactive_pane_hsb)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// If the bell is ringing, we draw another background layer over the
|
||||
// top of this in the configured bell color
|
||||
if let Some(intensity) = self.get_intensity_if_bell_target_ringing(
|
||||
&pos.pane,
|
||||
&config,
|
||||
VisualBellTarget::BackgroundColor,
|
||||
) {
|
||||
// target background color
|
||||
let LinearRgba(r, g, b, _) = config
|
||||
.resolved_palette
|
||||
.visual_bell
|
||||
.as_deref()
|
||||
.unwrap_or(&palette.foreground)
|
||||
.to_linear();
|
||||
|
||||
let background = if window_is_transparent {
|
||||
// for transparent windows, we fade in the target color
|
||||
// by adjusting its alpha
|
||||
LinearRgba::with_components(r, g, b, intensity)
|
||||
} else {
|
||||
// otherwise We'll interpolate between the background color
|
||||
// and the the target color
|
||||
let (r1, g1, b1, a) = palette
|
||||
.background
|
||||
.to_linear()
|
||||
.mul_alpha(config.window_background_opacity)
|
||||
.tuple();
|
||||
LinearRgba::with_components(
|
||||
r1 + (r - r1) * intensity,
|
||||
g1 + (g - g1) * intensity,
|
||||
b1 + (b - b1) * intensity,
|
||||
a,
|
||||
)
|
||||
};
|
||||
log::trace!("bell color is {:?}", background);
|
||||
|
||||
let mut quad = self.filled_rectangle(layers, 0, background_rect, background)?;
|
||||
|
||||
quad.set_hsv(if pos.is_active {
|
||||
None
|
||||
} else {
|
||||
Some(config.inactive_pane_hsb)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we only have a single scrollbar in a single position.
|
||||
// We only update it for the active pane, but we should probably
|
||||
// do a per-pane scrollbar. That will require more extensive
|
||||
// changes to ScrollHit, mouse positioning, PositionedPane
|
||||
// and tab size calculation.
|
||||
if pos.is_active && self.show_scroll_bar {
|
||||
let thumb_y_offset = top_bar_height as usize + border.top.get();
|
||||
|
||||
let min_height = self.min_scroll_bar_height();
|
||||
|
||||
let info = ScrollHit::thumb(
|
||||
&*pos.pane,
|
||||
current_viewport,
|
||||
self.dimensions.pixel_height.saturating_sub(
|
||||
thumb_y_offset + border.bottom.get() + bottom_bar_height as usize,
|
||||
),
|
||||
min_height as usize,
|
||||
);
|
||||
let abs_thumb_top = thumb_y_offset + info.top;
|
||||
let thumb_size = info.height;
|
||||
let color = palette.scrollbar_thumb.to_linear();
|
||||
|
||||
// Adjust the scrollbar thumb position
|
||||
let config = &self.config;
|
||||
let padding = self.effective_right_padding(&config) as f32;
|
||||
|
||||
let thumb_x = self.dimensions.pixel_width - padding as usize - border.right.get();
|
||||
|
||||
// Register the scroll bar location
|
||||
self.ui_items.push(UIItem {
|
||||
x: thumb_x,
|
||||
width: padding as usize,
|
||||
y: thumb_y_offset,
|
||||
height: info.top,
|
||||
item_type: UIItemType::AboveScrollThumb,
|
||||
});
|
||||
self.ui_items.push(UIItem {
|
||||
x: thumb_x,
|
||||
width: padding as usize,
|
||||
y: abs_thumb_top,
|
||||
height: thumb_size,
|
||||
item_type: UIItemType::ScrollThumb,
|
||||
});
|
||||
self.ui_items.push(UIItem {
|
||||
x: thumb_x,
|
||||
width: padding as usize,
|
||||
y: abs_thumb_top + thumb_size,
|
||||
height: self
|
||||
.dimensions
|
||||
.pixel_height
|
||||
.saturating_sub(abs_thumb_top + thumb_size),
|
||||
item_type: UIItemType::BelowScrollThumb,
|
||||
});
|
||||
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
2,
|
||||
euclid::rect(
|
||||
thumb_x as f32,
|
||||
abs_thumb_top as f32,
|
||||
padding,
|
||||
thumb_size as f32,
|
||||
),
|
||||
color,
|
||||
)?;
|
||||
}
|
||||
|
||||
let (selrange, rectangular) = {
|
||||
let sel = self.selection(pos.pane.pane_id());
|
||||
(sel.range.clone(), sel.rectangular)
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
let selection_fg = palette.selection_fg.to_linear();
|
||||
let selection_bg = palette.selection_bg.to_linear();
|
||||
let cursor_fg = palette.cursor_fg.to_linear();
|
||||
let cursor_bg = palette.cursor_bg.to_linear();
|
||||
let cursor_is_default_color =
|
||||
palette.cursor_fg == global_cursor_fg && palette.cursor_bg == global_cursor_bg;
|
||||
|
||||
{
|
||||
let stable_range = match current_viewport {
|
||||
Some(top) => top..top + dims.viewport_rows as StableRowIndex,
|
||||
None => dims.physical_top..dims.physical_top + dims.viewport_rows as StableRowIndex,
|
||||
};
|
||||
|
||||
pos.pane
|
||||
.apply_hyperlinks(stable_range.clone(), &self.config.hyperlink_rules);
|
||||
|
||||
struct LineRender<'a, 'b> {
|
||||
term_window: &'a mut crate::TermWindow,
|
||||
selrange: Option<SelectionRange>,
|
||||
rectangular: bool,
|
||||
dims: RenderableDimensions,
|
||||
top_pixel_y: f32,
|
||||
left_pixel_x: f32,
|
||||
pos: &'a PositionedPane,
|
||||
pane_id: PaneId,
|
||||
cursor: &'a StableCursorPosition,
|
||||
palette: &'a ColorPalette,
|
||||
default_bg: LinearRgba,
|
||||
cursor_border_color: LinearRgba,
|
||||
selection_fg: LinearRgba,
|
||||
selection_bg: LinearRgba,
|
||||
cursor_fg: LinearRgba,
|
||||
cursor_bg: LinearRgba,
|
||||
foreground: LinearRgba,
|
||||
cursor_is_default_color: bool,
|
||||
white_space: TextureRect,
|
||||
filled_box: TextureRect,
|
||||
window_is_transparent: bool,
|
||||
layers: &'a mut TripleLayerQuadAllocator<'b>,
|
||||
error: Option<anyhow::Error>,
|
||||
}
|
||||
|
||||
let left_pixel_x = padding_left
|
||||
+ border.left.get() as f32
|
||||
+ (pos.left as f32 * self.render_metrics.cell_size.width as f32);
|
||||
|
||||
let mut render = LineRender {
|
||||
term_window: self,
|
||||
selrange,
|
||||
rectangular,
|
||||
dims,
|
||||
top_pixel_y,
|
||||
left_pixel_x,
|
||||
pos,
|
||||
pane_id,
|
||||
cursor: &cursor,
|
||||
palette: &palette,
|
||||
cursor_border_color,
|
||||
selection_fg,
|
||||
selection_bg,
|
||||
cursor_fg,
|
||||
default_bg,
|
||||
cursor_bg,
|
||||
foreground,
|
||||
cursor_is_default_color,
|
||||
white_space,
|
||||
filled_box,
|
||||
window_is_transparent,
|
||||
layers,
|
||||
error: None,
|
||||
};
|
||||
|
||||
impl<'a, 'b> LineRender<'a, 'b> {
|
||||
fn render_line(
|
||||
&mut self,
|
||||
stable_top: StableRowIndex,
|
||||
line_idx: usize,
|
||||
line: &&mut Line,
|
||||
) -> anyhow::Result<()> {
|
||||
let stable_row = stable_top + line_idx as StableRowIndex;
|
||||
let selrange = self
|
||||
.selrange
|
||||
.map_or(0..0, |sel| sel.cols_for_row(stable_row, self.rectangular));
|
||||
// Constrain to the pane width!
|
||||
let selrange = selrange.start..selrange.end.min(self.dims.cols);
|
||||
|
||||
let (cursor, composing, password_input) = if self.cursor.y == stable_row {
|
||||
(
|
||||
Some(CursorProperties {
|
||||
position: StableCursorPosition {
|
||||
y: 0,
|
||||
..*self.cursor
|
||||
},
|
||||
dead_key_or_leader: self.term_window.dead_key_status
|
||||
!= DeadKeyStatus::None
|
||||
|| self.term_window.leader_is_active(),
|
||||
cursor_fg: self.cursor_fg,
|
||||
cursor_bg: self.cursor_bg,
|
||||
cursor_border_color: self.cursor_border_color,
|
||||
cursor_is_default_color: self.cursor_is_default_color,
|
||||
}),
|
||||
match (self.pos.is_active, &self.term_window.dead_key_status) {
|
||||
(true, DeadKeyStatus::Composing(composing)) => {
|
||||
Some(composing.to_string())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
if self.term_window.config.detect_password_input {
|
||||
match self.pos.pane.get_metadata() {
|
||||
Value::Object(obj) => {
|
||||
match obj.get(&Value::String("password_input".to_string()))
|
||||
{
|
||||
Some(Value::Bool(b)) => *b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(None, None, false)
|
||||
};
|
||||
|
||||
let shape_hash = self.term_window.shape_hash_for_line(line);
|
||||
|
||||
let quad_key = LineQuadCacheKey {
|
||||
pane_id: self.pane_id,
|
||||
password_input,
|
||||
pane_is_active: self.pos.is_active,
|
||||
config_generation: self.term_window.config.generation(),
|
||||
shape_generation: self.term_window.shape_generation,
|
||||
quad_generation: self.term_window.quad_generation,
|
||||
composing: composing.clone(),
|
||||
selection: selrange.clone(),
|
||||
cursor,
|
||||
shape_hash,
|
||||
top_pixel_y: NotNan::new(self.top_pixel_y).unwrap()
|
||||
+ (line_idx + self.pos.top) as f32
|
||||
* self.term_window.render_metrics.cell_size.height as f32,
|
||||
left_pixel_x: NotNan::new(self.left_pixel_x).unwrap(),
|
||||
phys_line_idx: line_idx,
|
||||
reverse_video: self.dims.reverse_video,
|
||||
};
|
||||
|
||||
if let Some(cached_quad) =
|
||||
self.term_window.line_quad_cache.borrow_mut().get(&quad_key)
|
||||
{
|
||||
let expired = cached_quad
|
||||
.expires
|
||||
.map(|i| Instant::now() >= i)
|
||||
.unwrap_or(false);
|
||||
let hover_changed = if cached_quad.invalidate_on_hover_change {
|
||||
!same_hyperlink(
|
||||
cached_quad.current_highlight.as_ref(),
|
||||
self.term_window.current_highlight.as_ref(),
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !expired && !hover_changed {
|
||||
cached_quad.layers.apply_to(self.layers)?;
|
||||
self.term_window.update_next_frame_time(cached_quad.expires);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = HeapQuadAllocator::default();
|
||||
let next_due = self.term_window.has_animation.borrow_mut().take();
|
||||
|
||||
let shape_key = LineToEleShapeCacheKey {
|
||||
shape_hash,
|
||||
shape_generation: quad_key.shape_generation,
|
||||
composing: if self.cursor.y == stable_row && self.pos.is_active {
|
||||
if let DeadKeyStatus::Composing(composing) =
|
||||
&self.term_window.dead_key_status
|
||||
{
|
||||
Some((self.cursor.x, composing.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
let render_result = self.term_window.render_screen_line_opengl(
|
||||
RenderScreenLineOpenGLParams {
|
||||
top_pixel_y: *quad_key.top_pixel_y,
|
||||
left_pixel_x: self.left_pixel_x,
|
||||
pixel_width: self.dims.cols as f32
|
||||
* self.term_window.render_metrics.cell_size.width as f32,
|
||||
stable_line_idx: Some(stable_row),
|
||||
line: &line,
|
||||
selection: selrange.clone(),
|
||||
cursor: &self.cursor,
|
||||
palette: &self.palette,
|
||||
dims: &self.dims,
|
||||
config: &self.term_window.config,
|
||||
cursor_border_color: self.cursor_border_color,
|
||||
foreground: self.foreground,
|
||||
is_active: self.pos.is_active,
|
||||
pane: Some(&self.pos.pane),
|
||||
selection_fg: self.selection_fg,
|
||||
selection_bg: self.selection_bg,
|
||||
cursor_fg: self.cursor_fg,
|
||||
cursor_bg: self.cursor_bg,
|
||||
cursor_is_default_color: self.cursor_is_default_color,
|
||||
white_space: self.white_space,
|
||||
filled_box: self.filled_box,
|
||||
window_is_transparent: self.window_is_transparent,
|
||||
default_bg: self.default_bg,
|
||||
font: None,
|
||||
style: None,
|
||||
use_pixel_positioning: self
|
||||
.term_window
|
||||
.config
|
||||
.experimental_pixel_positioning,
|
||||
render_metrics: self.term_window.render_metrics,
|
||||
shape_key: Some(shape_key),
|
||||
password_input,
|
||||
},
|
||||
&mut TripleLayerQuadAllocator::Heap(&mut buf),
|
||||
)?;
|
||||
|
||||
let expires = self.term_window.has_animation.borrow().as_ref().cloned();
|
||||
self.term_window.update_next_frame_time(next_due);
|
||||
|
||||
buf.apply_to(self.layers)?;
|
||||
|
||||
let quad_value = LineQuadCacheValue {
|
||||
layers: buf,
|
||||
expires,
|
||||
line: (*line).clone(),
|
||||
invalidate_on_hover_change: render_result.invalidate_on_hover_change,
|
||||
current_highlight: if render_result.invalidate_on_hover_change {
|
||||
self.term_window.current_highlight.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
self.term_window
|
||||
.line_quad_cache
|
||||
.borrow_mut()
|
||||
.put(quad_key, quad_value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> WithPaneLines for LineRender<'a, 'b> {
|
||||
fn with_lines_mut(&mut self, stable_top: StableRowIndex, lines: &mut [&mut Line]) {
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
if let Err(err) = self.render_line(stable_top, line_idx, line) {
|
||||
self.error.replace(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos.pane.with_lines_mut(stable_range.clone(), &mut render);
|
||||
if let Some(error) = render.error.take() {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if let Some(zone) = zone {
|
||||
// TODO: render a thingy to jump to prior prompt
|
||||
}
|
||||
*/
|
||||
metrics::histogram!("paint_pane_opengl.lines", start.elapsed());
|
||||
log::trace!("lines elapsed {:?}", start.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_pane(
|
||||
&mut self,
|
||||
pos: &PositionedPane,
|
||||
num_panes: usize,
|
||||
) -> anyhow::Result<ComputedElement> {
|
||||
// First compute the bounds for the pane background
|
||||
|
||||
let cell_width = self.render_metrics.cell_size.width as f32;
|
||||
let cell_height = self.render_metrics.cell_size.height as f32;
|
||||
let (padding_left, padding_top) = self.padding_left_top();
|
||||
let tab_bar_height = if self.show_tab_bar {
|
||||
self.tab_bar_pixel_height()?
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
let (top_bar_height, _bottom_bar_height) = if self.config.tab_bar_at_bottom {
|
||||
(0.0, tab_bar_height)
|
||||
} else {
|
||||
(tab_bar_height, 0.0)
|
||||
};
|
||||
|
||||
let border = self.get_os_border();
|
||||
let top_pixel_y = top_bar_height + padding_top + border.top.get() as f32;
|
||||
|
||||
// We want to fill out to the edges of the splits
|
||||
let (x, width_delta) = if pos.left == 0 {
|
||||
(
|
||||
0.,
|
||||
padding_left + border.left.get() as f32 + (cell_width / 2.0),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
padding_left + border.left.get() as f32 - (cell_width / 2.0)
|
||||
+ (pos.left as f32 * cell_width),
|
||||
cell_width,
|
||||
)
|
||||
};
|
||||
|
||||
let (y, height_delta) = if pos.top == 0 {
|
||||
(
|
||||
(top_pixel_y - padding_top),
|
||||
padding_top + (cell_height / 2.0),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
top_pixel_y + (pos.top as f32 * cell_height) - (cell_height / 2.0),
|
||||
cell_height,
|
||||
)
|
||||
};
|
||||
|
||||
let background_rect = euclid::rect(
|
||||
x,
|
||||
y,
|
||||
// Go all the way to the right edge if we're right-most
|
||||
if pos.left + pos.width >= self.terminal_size.cols as usize {
|
||||
self.dimensions.pixel_width as f32 - x
|
||||
} else {
|
||||
(pos.width as f32 * cell_width) + width_delta
|
||||
},
|
||||
// Go all the way to the bottom if we're bottom-most
|
||||
if pos.top + pos.height >= self.terminal_size.rows as usize {
|
||||
self.dimensions.pixel_height as f32 - y
|
||||
} else {
|
||||
(pos.height as f32 * cell_height) + height_delta as f32
|
||||
},
|
||||
);
|
||||
|
||||
// Bounds for the terminal cells
|
||||
let content_rect = euclid::rect(
|
||||
padding_left + border.left.get() as f32 - (cell_width / 2.0)
|
||||
+ (pos.left as f32 * cell_width),
|
||||
top_pixel_y + (pos.top as f32 * cell_height) - (cell_height / 2.0),
|
||||
pos.width as f32 * cell_width,
|
||||
pos.height as f32 * cell_height,
|
||||
);
|
||||
|
||||
let palette = pos.pane.palette();
|
||||
|
||||
// TODO: visual bell background layer
|
||||
// TODO: scrollbar
|
||||
|
||||
Ok(ComputedElement {
|
||||
item_type: None,
|
||||
zindex: 0,
|
||||
bounds: background_rect,
|
||||
border: PixelDimension::default(),
|
||||
border_rect: background_rect,
|
||||
border_corners: None,
|
||||
colors: ElementColors {
|
||||
border: BorderColor::default(),
|
||||
bg: if num_panes > 1 && self.window_background.is_empty() {
|
||||
palette
|
||||
.background
|
||||
.to_linear()
|
||||
.mul_alpha(self.config.window_background_opacity)
|
||||
.into()
|
||||
} else {
|
||||
InheritableColor::Inherited
|
||||
},
|
||||
text: InheritableColor::Inherited,
|
||||
},
|
||||
hover_colors: None,
|
||||
padding: background_rect,
|
||||
content_rect,
|
||||
baseline: 1.0,
|
||||
content: ComputedElementContent::Children(vec![]),
|
||||
})
|
||||
}
|
||||
}
|
890
wezterm-gui/src/termwindow/render/screen_line.rs
Normal file
890
wezterm-gui/src/termwindow/render/screen_line.rs
Normal file
@ -0,0 +1,890 @@
|
||||
use crate::quad::{QuadTrait, TripleLayerQuadAllocator, TripleLayerQuadAllocatorTrait};
|
||||
use crate::termwindow::render::{
|
||||
resolve_fg_color_attr, same_hyperlink, update_next_frame_time, ClusterStyleCache,
|
||||
ComputeCellFgBgParams, ComputeCellFgBgResult, LineToElementParams, LineToElementShape,
|
||||
RenderScreenLineOpenGLParams, RenderScreenLineOpenGLResult,
|
||||
};
|
||||
use crate::termwindow::LineToElementShapeItem;
|
||||
use ::window::DeadKeyStatus;
|
||||
use config::{HsbTransform, TextStyle};
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
use termwiz::cell::{unicode_column_width, Blink};
|
||||
use termwiz::color::LinearRgba;
|
||||
use wezterm_bidi::Direction;
|
||||
use wezterm_term::color::ColorAttribute;
|
||||
use wezterm_term::CellAttributes;
|
||||
|
||||
impl crate::TermWindow {
|
||||
/// "Render" a line of the terminal screen into the vertex buffer.
|
||||
/// This is nominally a matter of setting the fg/bg color and the
|
||||
/// texture coordinates for a given glyph. There's a little bit
|
||||
/// of extra complexity to deal with multi-cell glyphs.
|
||||
pub fn render_screen_line_opengl(
|
||||
&self,
|
||||
params: RenderScreenLineOpenGLParams,
|
||||
layers: &mut TripleLayerQuadAllocator,
|
||||
) -> anyhow::Result<RenderScreenLineOpenGLResult> {
|
||||
if params.line.is_double_height_bottom() {
|
||||
// The top and bottom lines are required to have the same content.
|
||||
// For the sake of simplicity, we render both of them as part of
|
||||
// rendering the top row, so we have nothing more to do here.
|
||||
return Ok(RenderScreenLineOpenGLResult {
|
||||
invalidate_on_hover_change: false,
|
||||
});
|
||||
}
|
||||
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
|
||||
let num_cols = params.dims.cols;
|
||||
|
||||
let hsv = if params.is_active {
|
||||
None
|
||||
} else {
|
||||
Some(params.config.inactive_pane_hsb)
|
||||
};
|
||||
|
||||
let width_scale = if !params.line.is_single_width() {
|
||||
2.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let height_scale = if params.line.is_double_height_top() {
|
||||
2.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let cell_width = params.render_metrics.cell_size.width as f32 * width_scale;
|
||||
let cell_height = params.render_metrics.cell_size.height as f32 * height_scale;
|
||||
let pos_y = (self.dimensions.pixel_height as f32 / -2.) + params.top_pixel_y;
|
||||
let gl_x = self.dimensions.pixel_width as f32 / -2.;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let cursor_idx = if params.pane.is_some()
|
||||
&& params.is_active
|
||||
&& params.stable_line_idx == Some(params.cursor.y)
|
||||
{
|
||||
Some(params.cursor.x)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Referencing the text being composed, but only if it belongs to this pane
|
||||
let composing = if cursor_idx.is_some() {
|
||||
if let DeadKeyStatus::Composing(composing) = &self.dead_key_status {
|
||||
Some(composing)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut composition_width = 0;
|
||||
|
||||
let (_bidi_enabled, bidi_direction) = params.line.bidi_info();
|
||||
let direction = bidi_direction.direction();
|
||||
|
||||
// Do we need to shape immediately, or can we use the pre-shaped data?
|
||||
if let Some(composing) = composing {
|
||||
composition_width = unicode_column_width(composing, None);
|
||||
}
|
||||
|
||||
let cursor_cell = if params.stable_line_idx == Some(params.cursor.y) {
|
||||
params.line.get_cell(params.cursor.x)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cursor_range = if composition_width > 0 {
|
||||
params.cursor.x..params.cursor.x + composition_width
|
||||
} else if params.stable_line_idx == Some(params.cursor.y) {
|
||||
params.cursor.x..params.cursor.x + cursor_cell.as_ref().map(|c| c.width()).unwrap_or(1)
|
||||
} else {
|
||||
0..0
|
||||
};
|
||||
|
||||
let cursor_range_pixels = params.left_pixel_x + cursor_range.start as f32 * cell_width
|
||||
..params.left_pixel_x + cursor_range.end as f32 * cell_width;
|
||||
|
||||
let mut shaped = None;
|
||||
let mut invalidate_on_hover_change = false;
|
||||
|
||||
if let Some(shape_key) = ¶ms.shape_key {
|
||||
let mut cache = self.line_to_ele_shape_cache.borrow_mut();
|
||||
if let Some(entry) = cache.get(shape_key) {
|
||||
let expired = entry.expires.map(|i| Instant::now() >= i).unwrap_or(false);
|
||||
let hover_changed = if entry.invalidate_on_hover_change {
|
||||
!same_hyperlink(
|
||||
entry.current_highlight.as_ref(),
|
||||
self.current_highlight.as_ref(),
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !expired && !hover_changed {
|
||||
self.update_next_frame_time(entry.expires);
|
||||
shaped.replace(Rc::clone(&entry.shaped));
|
||||
}
|
||||
|
||||
invalidate_on_hover_change = entry.invalidate_on_hover_change;
|
||||
}
|
||||
}
|
||||
|
||||
let shaped = if let Some(shaped) = shaped {
|
||||
shaped
|
||||
} else {
|
||||
let params = LineToElementParams {
|
||||
config: params.config,
|
||||
line: params.line,
|
||||
cursor: params.cursor,
|
||||
palette: params.palette,
|
||||
stable_line_idx: params.stable_line_idx.unwrap_or(0),
|
||||
window_is_transparent: params.window_is_transparent,
|
||||
reverse_video: params.dims.reverse_video,
|
||||
shape_key: ¶ms.shape_key,
|
||||
};
|
||||
|
||||
let (shaped, invalidate_on_hover) = self.build_line_element_shape(params)?;
|
||||
invalidate_on_hover_change = invalidate_on_hover;
|
||||
shaped
|
||||
};
|
||||
|
||||
let bounding_rect = euclid::rect(
|
||||
params.left_pixel_x,
|
||||
params.top_pixel_y,
|
||||
params.pixel_width,
|
||||
cell_height,
|
||||
);
|
||||
|
||||
fn phys(x: usize, num_cols: usize, direction: Direction) -> usize {
|
||||
match direction {
|
||||
Direction::LeftToRight => x,
|
||||
Direction::RightToLeft => num_cols - x,
|
||||
}
|
||||
}
|
||||
|
||||
if params.dims.reverse_video {
|
||||
let mut quad = self.filled_rectangle(
|
||||
layers,
|
||||
0,
|
||||
euclid::rect(
|
||||
params.left_pixel_x,
|
||||
params.top_pixel_y,
|
||||
params.pixel_width,
|
||||
cell_height,
|
||||
),
|
||||
params.foreground,
|
||||
)?;
|
||||
quad.set_hsv(hsv);
|
||||
}
|
||||
|
||||
// Assume that we are drawing retro tab bar if there is no
|
||||
// stable_line_idx set.
|
||||
let is_tab_bar = params.stable_line_idx.is_none();
|
||||
|
||||
// Make a pass to compute background colors.
|
||||
// Need to consider:
|
||||
// * background when it is not the default color
|
||||
// * Reverse video attribute
|
||||
for item in shaped.iter() {
|
||||
let cluster = &item.cluster;
|
||||
let attrs = &cluster.attrs;
|
||||
let cluster_width = cluster.width;
|
||||
|
||||
let bg_is_default = attrs.background() == ColorAttribute::Default;
|
||||
let bg_color = params.palette.resolve_bg(attrs.background()).to_linear();
|
||||
|
||||
let fg_color = resolve_fg_color_attr(
|
||||
&attrs,
|
||||
attrs.foreground(),
|
||||
¶ms.palette,
|
||||
¶ms.config,
|
||||
&Default::default(),
|
||||
);
|
||||
|
||||
let (bg_color, bg_is_default) = {
|
||||
let mut fg = fg_color;
|
||||
let mut bg = bg_color;
|
||||
let mut bg_default = bg_is_default;
|
||||
|
||||
// Check the line reverse_video flag and flip.
|
||||
if attrs.reverse() == !params.dims.reverse_video {
|
||||
std::mem::swap(&mut fg, &mut bg);
|
||||
bg_default = false;
|
||||
}
|
||||
|
||||
(
|
||||
bg.mul_alpha(self.config.text_background_opacity),
|
||||
bg_default,
|
||||
)
|
||||
};
|
||||
|
||||
if !bg_is_default {
|
||||
let x = params.left_pixel_x
|
||||
+ if params.use_pixel_positioning {
|
||||
item.x_pos
|
||||
} else {
|
||||
phys(cluster.first_cell_idx, num_cols, direction) as f32 * cell_width
|
||||
};
|
||||
|
||||
let mut width = if params.use_pixel_positioning {
|
||||
item.pixel_width
|
||||
} else {
|
||||
cluster_width as f32 * cell_width
|
||||
};
|
||||
|
||||
// If the tab bar is falling just short of the full width of the
|
||||
// window, extend it to fit.
|
||||
// <https://github.com/wez/wezterm/issues/2210>
|
||||
if is_tab_bar && (x + width + cell_width) > params.pixel_width {
|
||||
width += cell_width;
|
||||
}
|
||||
|
||||
let rect = euclid::rect(x, params.top_pixel_y, width, cell_height);
|
||||
if let Some(rect) = rect.intersection(&bounding_rect) {
|
||||
let mut quad = self.filled_rectangle(layers, 0, rect, bg_color)?;
|
||||
quad.set_hsv(hsv);
|
||||
}
|
||||
}
|
||||
|
||||
// Underlines
|
||||
if item.underline_tex_rect != params.white_space {
|
||||
// Draw one per cell, otherwise curly underlines
|
||||
// stretch across the whole span
|
||||
for i in 0..cluster_width {
|
||||
let mut quad = layers.allocate(0)?;
|
||||
let x = gl_x
|
||||
+ params.left_pixel_x
|
||||
+ if params.use_pixel_positioning {
|
||||
item.x_pos
|
||||
} else {
|
||||
phys(cluster.first_cell_idx + i, num_cols, direction) as f32
|
||||
* cell_width
|
||||
};
|
||||
|
||||
quad.set_position(x, pos_y, x + cell_width, pos_y + cell_height);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_has_color(false);
|
||||
quad.set_texture(item.underline_tex_rect);
|
||||
quad.set_fg_color(item.underline_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the selection background color.
|
||||
// This always uses a physical x position, regardles of the line
|
||||
// direction.
|
||||
let selection_pixel_range = if !params.selection.is_empty() {
|
||||
let start = params.left_pixel_x + (params.selection.start as f32 * cell_width);
|
||||
let width = (params.selection.end - params.selection.start) as f32 * cell_width;
|
||||
let mut quad = self.filled_rectangle(
|
||||
layers,
|
||||
0,
|
||||
euclid::rect(start, params.top_pixel_y, width, cell_height),
|
||||
params.selection_bg,
|
||||
)?;
|
||||
|
||||
quad.set_hsv(hsv);
|
||||
|
||||
start..start + width
|
||||
} else {
|
||||
0.0..0.0
|
||||
};
|
||||
|
||||
// Consider cursor
|
||||
if !cursor_range.is_empty() {
|
||||
let (fg_color, bg_color) = if let Some(c) = &cursor_cell {
|
||||
let attrs = c.attrs();
|
||||
|
||||
let bg_color = params.palette.resolve_bg(attrs.background()).to_linear();
|
||||
|
||||
let fg_color = resolve_fg_color_attr(
|
||||
&attrs,
|
||||
attrs.foreground(),
|
||||
¶ms.palette,
|
||||
¶ms.config,
|
||||
&Default::default(),
|
||||
);
|
||||
|
||||
(fg_color, bg_color)
|
||||
} else {
|
||||
(params.foreground, params.default_bg)
|
||||
};
|
||||
|
||||
let ComputeCellFgBgResult {
|
||||
cursor_shape,
|
||||
cursor_border_color,
|
||||
cursor_border_color_alt,
|
||||
cursor_border_mix,
|
||||
..
|
||||
} = self.compute_cell_fg_bg(ComputeCellFgBgParams {
|
||||
cursor: Some(params.cursor),
|
||||
selected: false,
|
||||
fg_color,
|
||||
bg_color,
|
||||
is_active_pane: params.is_active,
|
||||
config: params.config,
|
||||
selection_fg: params.selection_fg,
|
||||
selection_bg: params.selection_bg,
|
||||
cursor_fg: params.cursor_fg,
|
||||
cursor_bg: params.cursor_bg,
|
||||
cursor_is_default_color: params.cursor_is_default_color,
|
||||
cursor_border_color: params.cursor_border_color,
|
||||
pane: params.pane,
|
||||
});
|
||||
let pos_x = (self.dimensions.pixel_width as f32 / -2.)
|
||||
+ params.left_pixel_x
|
||||
+ (phys(params.cursor.x, num_cols, direction) as f32 * cell_width);
|
||||
|
||||
if cursor_shape.is_some() {
|
||||
let mut quad = layers.allocate(0)?;
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_has_color(false);
|
||||
|
||||
let mut draw_basic = true;
|
||||
|
||||
if params.password_input {
|
||||
let attrs = cursor_cell
|
||||
.as_ref()
|
||||
.map(|cell| cell.attrs().clone())
|
||||
.unwrap_or_else(|| CellAttributes::blank());
|
||||
|
||||
let glyph = self.resolve_lock_glyph(
|
||||
&TextStyle::default(),
|
||||
&attrs,
|
||||
params.font.as_ref(),
|
||||
gl_state,
|
||||
¶ms.render_metrics,
|
||||
)?;
|
||||
|
||||
if let Some(sprite) = &glyph.texture {
|
||||
let width = sprite.coords.size.width as f32 * glyph.scale as f32;
|
||||
let height =
|
||||
sprite.coords.size.height as f32 * glyph.scale as f32 * height_scale;
|
||||
|
||||
let pos_y = pos_y
|
||||
+ cell_height
|
||||
+ (params.render_metrics.descender.get() as f32
|
||||
- (glyph.y_offset + glyph.bearing_y).get() as f32)
|
||||
* height_scale;
|
||||
|
||||
let pos_x = pos_x + (glyph.x_offset + glyph.bearing_x).get() as f32;
|
||||
quad.set_position(pos_x, pos_y, pos_x + width, pos_y + height);
|
||||
quad.set_texture(sprite.texture_coords());
|
||||
draw_basic = false;
|
||||
}
|
||||
}
|
||||
|
||||
if draw_basic {
|
||||
quad.set_position(
|
||||
pos_x,
|
||||
pos_y,
|
||||
pos_x + (cursor_range.end - cursor_range.start) as f32 * cell_width,
|
||||
pos_y + cell_height,
|
||||
);
|
||||
quad.set_texture(
|
||||
gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cursor_sprite(
|
||||
cursor_shape,
|
||||
¶ms.render_metrics,
|
||||
(cursor_range.end - cursor_range.start) as u8,
|
||||
)?
|
||||
.texture_coords(),
|
||||
);
|
||||
}
|
||||
|
||||
quad.set_fg_color(cursor_border_color);
|
||||
quad.set_alt_color_and_mix_value(cursor_border_color_alt, cursor_border_mix);
|
||||
}
|
||||
}
|
||||
|
||||
let mut overlay_images = vec![];
|
||||
|
||||
// Number of cells we've rendered, starting from the edge of the line
|
||||
let mut visual_cell_idx = 0;
|
||||
|
||||
let mut cluster_x_pos = match direction {
|
||||
Direction::LeftToRight => 0.,
|
||||
Direction::RightToLeft => params.pixel_width,
|
||||
};
|
||||
|
||||
for item in shaped.iter() {
|
||||
let cluster = &item.cluster;
|
||||
let glyph_info = &item.glyph_info;
|
||||
let images = cluster.attrs.images().unwrap_or_else(|| vec![]);
|
||||
let valign_adjust = match cluster.attrs.vertical_align() {
|
||||
termwiz::cell::VerticalAlign::BaseLine => 0.,
|
||||
termwiz::cell::VerticalAlign::SuperScript => {
|
||||
params.render_metrics.cell_size.height as f32 * -0.25
|
||||
}
|
||||
termwiz::cell::VerticalAlign::SubScript => {
|
||||
params.render_metrics.cell_size.height as f32 * 0.25
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: remember logical/visual mapping for selection
|
||||
#[allow(unused_variables)]
|
||||
let mut phys_cell_idx = cluster.first_cell_idx;
|
||||
|
||||
// Pre-decrement by the cluster width when doing RTL,
|
||||
// so that we can render it right-justified
|
||||
if direction == Direction::RightToLeft {
|
||||
cluster_x_pos -= if params.use_pixel_positioning {
|
||||
item.pixel_width
|
||||
} else {
|
||||
cluster.width as f32 * cell_width
|
||||
};
|
||||
}
|
||||
|
||||
for info in glyph_info.iter() {
|
||||
let glyph = &info.glyph;
|
||||
|
||||
if params.use_pixel_positioning
|
||||
&& params.left_pixel_x + cluster_x_pos + glyph.x_advance.get() as f32
|
||||
>= params.left_pixel_x + params.pixel_width
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for glyph_idx in 0..info.pos.num_cells as usize {
|
||||
for img in &images {
|
||||
if img.z_index() < 0 {
|
||||
self.populate_image_quad(
|
||||
&img,
|
||||
gl_state,
|
||||
layers,
|
||||
0,
|
||||
visual_cell_idx + glyph_idx,
|
||||
¶ms,
|
||||
hsv,
|
||||
item.fg_color,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// First, resolve this glyph to a texture
|
||||
let mut texture = glyph.texture.as_ref().cloned();
|
||||
|
||||
let mut top = cell_height
|
||||
+ (params.render_metrics.descender.get() as f32 + valign_adjust
|
||||
- (glyph.y_offset + glyph.bearing_y).get() as f32)
|
||||
* height_scale;
|
||||
|
||||
if self.config.custom_block_glyphs {
|
||||
if let Some(block) = &info.block_key {
|
||||
texture.replace(
|
||||
gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_block(*block, ¶ms.render_metrics)?,
|
||||
);
|
||||
// Custom glyphs don't have the same offsets as computed
|
||||
// by the shaper, and are rendered relative to the cell
|
||||
// top left, rather than the baseline.
|
||||
top = 0.;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(texture) = texture {
|
||||
// TODO: clipping, but we can do that based on pixels
|
||||
|
||||
let pos_x = cluster_x_pos
|
||||
+ if params.use_pixel_positioning {
|
||||
(glyph.x_offset + glyph.bearing_x).get() as f32
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
if pos_x > params.pixel_width {
|
||||
log::trace!("breaking on overflow {} > {}", pos_x, params.pixel_width);
|
||||
break;
|
||||
}
|
||||
let pos_x = pos_x + params.left_pixel_x;
|
||||
|
||||
// We need to conceptually slice this texture into
|
||||
// up into strips that consider the cursor and selection
|
||||
// background ranges. For ligatures that span cells, we'll
|
||||
// need to explicitly render each strip independently so that
|
||||
// we can set its foreground color to the appropriate color
|
||||
// for the cursor/selection/regular background upon which
|
||||
// it will be drawn.
|
||||
|
||||
/// Computes the intersection between r1 and r2.
|
||||
/// It may be empty.
|
||||
fn intersection(r1: &Range<f32>, r2: &Range<f32>) -> Range<f32> {
|
||||
let start = r1.start.max(r2.start);
|
||||
let end = r1.end.min(r2.end);
|
||||
if end > start {
|
||||
start..end
|
||||
} else {
|
||||
// Empty
|
||||
start..start
|
||||
}
|
||||
}
|
||||
|
||||
/// Assess range `r` relative to `within`. If `r` intersects
|
||||
/// `within` then return the 3 ranges that are subsets of `r`
|
||||
/// which are to the left of `within`, intersecting `within`
|
||||
/// and to the right of `within`.
|
||||
/// If `r` and `within` do not intersect, returns `r` and
|
||||
/// two empty ranges.
|
||||
/// If `r` is itself an empty range, all returned ranges
|
||||
/// will be empty.
|
||||
fn range3(
|
||||
r: &Range<f32>,
|
||||
within: &Range<f32>,
|
||||
) -> (Range<f32>, Range<f32>, Range<f32>) {
|
||||
if r.is_empty() {
|
||||
return (r.clone(), r.clone(), r.clone());
|
||||
}
|
||||
let i = intersection(r, within);
|
||||
if i.is_empty() {
|
||||
return (r.clone(), i.clone(), i.clone());
|
||||
}
|
||||
|
||||
let left = if i.start > r.start {
|
||||
r.start..i.start
|
||||
} else {
|
||||
r.start..r.start
|
||||
};
|
||||
|
||||
let right = if i.end < r.end {
|
||||
i.end..r.end
|
||||
} else {
|
||||
r.end..r.end
|
||||
};
|
||||
|
||||
(left, i, right)
|
||||
}
|
||||
|
||||
let adjust = (glyph.x_offset + glyph.bearing_x).get() as f32;
|
||||
let texture_range = pos_x + adjust
|
||||
..pos_x + adjust + (texture.coords.size.width as f32 * width_scale);
|
||||
|
||||
// First bucket the ranges according to cursor position
|
||||
let (left, mid, right) = range3(&texture_range, &cursor_range_pixels);
|
||||
// Then sub-divide the non-cursor ranges according to selection
|
||||
let (la, lb, lc) = range3(&left, &selection_pixel_range);
|
||||
let (ra, rb, rc) = range3(&right, &selection_pixel_range);
|
||||
|
||||
// and render each of these strips
|
||||
for range in [la, lb, lc, mid, ra, rb, rc] {
|
||||
if range.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_cursor = cursor_range_pixels.contains(&range.start);
|
||||
let selected =
|
||||
!is_cursor && selection_pixel_range.contains(&range.start);
|
||||
|
||||
let ComputeCellFgBgResult {
|
||||
fg_color: glyph_color,
|
||||
bg_color,
|
||||
fg_color_alt,
|
||||
fg_color_mix,
|
||||
..
|
||||
} = self.compute_cell_fg_bg(ComputeCellFgBgParams {
|
||||
cursor: if is_cursor { Some(params.cursor) } else { None },
|
||||
selected,
|
||||
fg_color: item.fg_color,
|
||||
bg_color: item.bg_color,
|
||||
is_active_pane: params.is_active,
|
||||
config: params.config,
|
||||
selection_fg: params.selection_fg,
|
||||
selection_bg: params.selection_bg,
|
||||
cursor_fg: params.cursor_fg,
|
||||
cursor_bg: params.cursor_bg,
|
||||
cursor_is_default_color: params.cursor_is_default_color,
|
||||
cursor_border_color: params.cursor_border_color,
|
||||
pane: params.pane,
|
||||
});
|
||||
|
||||
if glyph_color == bg_color || cluster.attrs.invisible() {
|
||||
// Essentially invisible: don't render it, as anti-aliasing
|
||||
// can cause a ghostly outline of the invisible glyph to appear.
|
||||
continue;
|
||||
}
|
||||
|
||||
let pixel_rect = euclid::rect(
|
||||
texture.coords.origin.x + (range.start - (pos_x + adjust)) as isize,
|
||||
texture.coords.origin.y,
|
||||
((range.end - range.start) / width_scale) as isize,
|
||||
texture.coords.size.height,
|
||||
);
|
||||
|
||||
let texture_rect = texture.texture.to_texture_coords(pixel_rect);
|
||||
|
||||
let mut quad = layers.allocate(1)?;
|
||||
quad.set_position(
|
||||
gl_x + range.start,
|
||||
pos_y + top,
|
||||
gl_x + range.end,
|
||||
pos_y + top + texture.coords.size.height as f32 * height_scale,
|
||||
);
|
||||
quad.set_fg_color(glyph_color);
|
||||
quad.set_alt_color_and_mix_value(fg_color_alt, fg_color_mix);
|
||||
quad.set_texture(texture_rect);
|
||||
quad.set_hsv(if glyph.brightness_adjust != 1.0 {
|
||||
let hsv = hsv.unwrap_or_else(|| HsbTransform::default());
|
||||
Some(HsbTransform {
|
||||
brightness: hsv.brightness * glyph.brightness_adjust,
|
||||
..hsv
|
||||
})
|
||||
} else {
|
||||
hsv
|
||||
});
|
||||
quad.set_has_color(glyph.has_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for glyph_idx in 0..info.pos.num_cells as usize {
|
||||
for img in &images {
|
||||
if img.z_index() >= 0 {
|
||||
overlay_images.push((
|
||||
visual_cell_idx + glyph_idx,
|
||||
img.clone(),
|
||||
item.fg_color,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
phys_cell_idx += info.pos.num_cells as usize;
|
||||
visual_cell_idx += info.pos.num_cells as usize;
|
||||
cluster_x_pos += if params.use_pixel_positioning {
|
||||
glyph.x_advance.get() as f32 * width_scale
|
||||
} else {
|
||||
info.pos.num_cells as f32 * cell_width
|
||||
};
|
||||
}
|
||||
|
||||
match direction {
|
||||
Direction::RightToLeft => {
|
||||
// And decrement it again
|
||||
cluster_x_pos -= if params.use_pixel_positioning {
|
||||
item.pixel_width * width_scale
|
||||
} else {
|
||||
cluster.width as f32 * cell_width
|
||||
};
|
||||
}
|
||||
Direction::LeftToRight => {}
|
||||
}
|
||||
}
|
||||
|
||||
for (cell_idx, img, glyph_color) in overlay_images {
|
||||
self.populate_image_quad(
|
||||
&img,
|
||||
gl_state,
|
||||
layers,
|
||||
2,
|
||||
phys(cell_idx, num_cols, direction),
|
||||
¶ms,
|
||||
hsv,
|
||||
glyph_color,
|
||||
)?;
|
||||
}
|
||||
|
||||
metrics::histogram!("render_screen_line_opengl", start.elapsed());
|
||||
|
||||
Ok(RenderScreenLineOpenGLResult {
|
||||
invalidate_on_hover_change,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_line_element_shape(
|
||||
&self,
|
||||
params: LineToElementParams,
|
||||
) -> anyhow::Result<(Rc<Vec<LineToElementShape>>, bool)> {
|
||||
let (bidi_enabled, bidi_direction) = params.line.bidi_info();
|
||||
let bidi_hint = if bidi_enabled {
|
||||
Some(bidi_direction)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cell_clusters = if let Some((cursor_x, composing)) =
|
||||
params.shape_key.as_ref().and_then(|k| k.composing.as_ref())
|
||||
{
|
||||
// Create an updated line with the composition overlaid
|
||||
let mut line = params.line.clone();
|
||||
let seqno = line.current_seqno();
|
||||
line.overlay_text_with_attribute(*cursor_x, &composing, CellAttributes::blank(), seqno);
|
||||
line.cluster(bidi_hint)
|
||||
} else {
|
||||
params.line.cluster(bidi_hint)
|
||||
};
|
||||
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let mut shaped = vec![];
|
||||
let mut last_style = None;
|
||||
let mut x_pos = 0.;
|
||||
let mut expires = None;
|
||||
let mut invalidate_on_hover_change = false;
|
||||
|
||||
for cluster in &cell_clusters {
|
||||
if !matches!(last_style.as_ref(), Some(ClusterStyleCache{attrs,..}) if *attrs == &cluster.attrs)
|
||||
{
|
||||
let attrs = &cluster.attrs;
|
||||
let style = self.fonts.match_style(params.config, attrs);
|
||||
let hyperlink = attrs.hyperlink();
|
||||
let is_highlited_hyperlink =
|
||||
same_hyperlink(hyperlink, self.current_highlight.as_ref());
|
||||
if hyperlink.is_some() {
|
||||
invalidate_on_hover_change = true;
|
||||
}
|
||||
// underline and strikethrough
|
||||
let underline_tex_rect = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_line_sprite(
|
||||
is_highlited_hyperlink,
|
||||
attrs.strikethrough(),
|
||||
attrs.underline(),
|
||||
attrs.overline(),
|
||||
&self.render_metrics,
|
||||
)?
|
||||
.texture_coords();
|
||||
let bg_is_default = attrs.background() == ColorAttribute::Default;
|
||||
let bg_color = params.palette.resolve_bg(attrs.background()).to_linear();
|
||||
|
||||
let fg_color = resolve_fg_color_attr(
|
||||
&attrs,
|
||||
attrs.foreground(),
|
||||
¶ms.palette,
|
||||
¶ms.config,
|
||||
style,
|
||||
);
|
||||
let (fg_color, bg_color, bg_is_default) = {
|
||||
let mut fg = fg_color;
|
||||
let mut bg = bg_color;
|
||||
let mut bg_default = bg_is_default;
|
||||
|
||||
// Check the line reverse_video flag and flip.
|
||||
if attrs.reverse() == !params.reverse_video {
|
||||
std::mem::swap(&mut fg, &mut bg);
|
||||
bg_default = false;
|
||||
}
|
||||
|
||||
// Check for blink, and if this is the "not-visible"
|
||||
// part of blinking then set fg = bg. This is a cheap
|
||||
// means of getting it done without impacting other
|
||||
// features.
|
||||
let blink_rate = match attrs.blink() {
|
||||
Blink::None => None,
|
||||
Blink::Slow => {
|
||||
Some((params.config.text_blink_rate, self.blink_state.borrow_mut()))
|
||||
}
|
||||
Blink::Rapid => Some((
|
||||
params.config.text_blink_rate_rapid,
|
||||
self.rapid_blink_state.borrow_mut(),
|
||||
)),
|
||||
};
|
||||
if let Some((blink_rate, mut colorease)) = blink_rate {
|
||||
if blink_rate != 0 {
|
||||
let (intensity, next) = colorease.intensity_continuous();
|
||||
|
||||
let (r1, g1, b1, a) = bg.tuple();
|
||||
let (r, g, b, _a) = fg.tuple();
|
||||
fg = LinearRgba::with_components(
|
||||
r1 + (r - r1) * intensity,
|
||||
g1 + (g - g1) * intensity,
|
||||
b1 + (b - b1) * intensity,
|
||||
a,
|
||||
);
|
||||
|
||||
update_next_frame_time(&mut expires, Some(next));
|
||||
self.update_next_frame_time(Some(next));
|
||||
}
|
||||
}
|
||||
|
||||
(fg, bg, bg_default)
|
||||
};
|
||||
|
||||
let glyph_color = fg_color;
|
||||
let underline_color = match attrs.underline_color() {
|
||||
ColorAttribute::Default => fg_color,
|
||||
c => resolve_fg_color_attr(&attrs, c, ¶ms.palette, ¶ms.config, style),
|
||||
};
|
||||
|
||||
let (bg_r, bg_g, bg_b, _) = bg_color.tuple();
|
||||
let bg_color = LinearRgba::with_components(
|
||||
bg_r,
|
||||
bg_g,
|
||||
bg_b,
|
||||
if params.window_is_transparent && bg_is_default {
|
||||
0.0
|
||||
} else {
|
||||
params.config.text_background_opacity
|
||||
},
|
||||
);
|
||||
|
||||
last_style.replace(ClusterStyleCache {
|
||||
attrs,
|
||||
style,
|
||||
underline_tex_rect: underline_tex_rect.clone(),
|
||||
bg_color,
|
||||
fg_color: glyph_color,
|
||||
underline_color,
|
||||
});
|
||||
}
|
||||
|
||||
let style_params = last_style.as_ref().expect("we just set it up").clone();
|
||||
|
||||
let glyph_info = self.cached_cluster_shape(
|
||||
style_params.style,
|
||||
&cluster,
|
||||
&gl_state,
|
||||
None,
|
||||
&self.render_metrics,
|
||||
)?;
|
||||
let pixel_width = glyph_info
|
||||
.iter()
|
||||
.map(|info| info.glyph.x_advance.get() as f32)
|
||||
.sum();
|
||||
|
||||
shaped.push(LineToElementShape {
|
||||
attrs: style_params.attrs.clone(),
|
||||
style: style_params.style.clone(),
|
||||
underline_tex_rect: style_params.underline_tex_rect,
|
||||
bg_color: style_params.bg_color,
|
||||
fg_color: style_params.fg_color,
|
||||
underline_color: style_params.underline_color,
|
||||
pixel_width,
|
||||
cluster: cluster.clone(),
|
||||
glyph_info,
|
||||
x_pos,
|
||||
});
|
||||
|
||||
x_pos += pixel_width;
|
||||
}
|
||||
|
||||
let shaped = Rc::new(shaped);
|
||||
|
||||
if let Some(shape_key) = params.shape_key {
|
||||
self.line_to_ele_shape_cache.borrow_mut().put(
|
||||
shape_key.clone(),
|
||||
LineToElementShapeItem {
|
||||
expires,
|
||||
shaped: Rc::clone(&shaped),
|
||||
invalidate_on_hover_change,
|
||||
current_highlight: if invalidate_on_hover_change {
|
||||
self.current_highlight.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok((shaped, invalidate_on_hover_change))
|
||||
}
|
||||
}
|
81
wezterm-gui/src/termwindow/render/split.rs
Normal file
81
wezterm-gui/src/termwindow/render/split.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use crate::termwindow::render::TripleLayerQuadAllocator;
|
||||
use crate::termwindow::{UIItem, UIItemType};
|
||||
use mux::pane::Pane;
|
||||
use mux::tab::{PositionedSplit, SplitDirection};
|
||||
use std::sync::Arc;
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn paint_split_opengl(
|
||||
&mut self,
|
||||
layers: &mut TripleLayerQuadAllocator,
|
||||
split: &PositionedSplit,
|
||||
pane: &Arc<dyn Pane>,
|
||||
) -> anyhow::Result<()> {
|
||||
let palette = pane.palette();
|
||||
let foreground = palette.split.to_linear();
|
||||
let cell_width = self.render_metrics.cell_size.width as f32;
|
||||
let cell_height = self.render_metrics.cell_size.height as f32;
|
||||
|
||||
let border = self.get_os_border();
|
||||
let first_row_offset = if self.show_tab_bar && !self.config.tab_bar_at_bottom {
|
||||
self.tab_bar_pixel_height()?
|
||||
} else {
|
||||
0.
|
||||
} + border.top.get() as f32;
|
||||
|
||||
let (padding_left, padding_top) = self.padding_left_top();
|
||||
|
||||
let pos_y = split.top as f32 * cell_height + first_row_offset + padding_top;
|
||||
let pos_x = split.left as f32 * cell_width + padding_left + border.left.get() as f32;
|
||||
|
||||
if split.direction == SplitDirection::Horizontal {
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
2,
|
||||
euclid::rect(
|
||||
pos_x + (cell_width / 2.0),
|
||||
pos_y - (cell_height / 2.0),
|
||||
self.render_metrics.underline_height as f32,
|
||||
(1. + split.size as f32) * cell_height,
|
||||
),
|
||||
foreground,
|
||||
)?;
|
||||
self.ui_items.push(UIItem {
|
||||
x: border.left.get() as usize
|
||||
+ padding_left as usize
|
||||
+ (split.left * cell_width as usize),
|
||||
width: cell_width as usize,
|
||||
y: padding_top as usize
|
||||
+ first_row_offset as usize
|
||||
+ split.top * cell_height as usize,
|
||||
height: split.size * cell_height as usize,
|
||||
item_type: UIItemType::Split(split.clone()),
|
||||
});
|
||||
} else {
|
||||
self.filled_rectangle(
|
||||
layers,
|
||||
2,
|
||||
euclid::rect(
|
||||
pos_x - (cell_width / 2.0),
|
||||
pos_y + (cell_height / 2.0),
|
||||
(1.0 + split.size as f32) * cell_width,
|
||||
self.render_metrics.underline_height as f32,
|
||||
),
|
||||
foreground,
|
||||
)?;
|
||||
self.ui_items.push(UIItem {
|
||||
x: border.left.get() as usize
|
||||
+ padding_left as usize
|
||||
+ (split.left * cell_width as usize),
|
||||
width: split.size * cell_width as usize,
|
||||
y: padding_top as usize
|
||||
+ first_row_offset as usize
|
||||
+ split.top * cell_height as usize,
|
||||
height: cell_height as usize,
|
||||
item_type: UIItemType::Split(split.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
119
wezterm-gui/src/termwindow/render/tab_bar.rs
Normal file
119
wezterm-gui/src/termwindow/render/tab_bar.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use crate::quad::TripleLayerQuadAllocator;
|
||||
use crate::termwindow::render::RenderScreenLineOpenGLParams;
|
||||
use crate::utilsprites::RenderMetrics;
|
||||
use config::ConfigHandle;
|
||||
use mux::renderable::RenderableDimensions;
|
||||
use wezterm_term::color::ColorAttribute;
|
||||
use window::color::LinearRgba;
|
||||
|
||||
impl crate::TermWindow {
|
||||
pub fn paint_tab_bar(&mut self, layers: &mut TripleLayerQuadAllocator) -> anyhow::Result<()> {
|
||||
if self.config.use_fancy_tab_bar {
|
||||
if self.fancy_tab_bar.is_none() {
|
||||
let palette = self.palette().clone();
|
||||
let tab_bar = self.build_fancy_tab_bar(&palette)?;
|
||||
self.fancy_tab_bar.replace(tab_bar);
|
||||
}
|
||||
|
||||
self.ui_items.append(&mut self.paint_fancy_tab_bar()?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let border = self.get_os_border();
|
||||
|
||||
let palette = self.palette().clone();
|
||||
let tab_bar_height = self.tab_bar_pixel_height()?;
|
||||
let tab_bar_y = if self.config.tab_bar_at_bottom {
|
||||
((self.dimensions.pixel_height as f32) - (tab_bar_height + border.bottom.get() as f32))
|
||||
.max(0.)
|
||||
} else {
|
||||
border.top.get() as f32
|
||||
};
|
||||
|
||||
// Register the tab bar location
|
||||
self.ui_items.append(&mut self.tab_bar.compute_ui_items(
|
||||
tab_bar_y as usize,
|
||||
self.render_metrics.cell_size.height as usize,
|
||||
self.render_metrics.cell_size.width as usize,
|
||||
));
|
||||
|
||||
let window_is_transparent =
|
||||
!self.window_background.is_empty() || self.config.window_background_opacity != 1.0;
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let white_space = gl_state.util_sprites.white_space.texture_coords();
|
||||
let filled_box = gl_state.util_sprites.filled_box.texture_coords();
|
||||
let default_bg = palette
|
||||
.resolve_bg(ColorAttribute::Default)
|
||||
.to_linear()
|
||||
.mul_alpha(if window_is_transparent {
|
||||
0.
|
||||
} else {
|
||||
self.config.text_background_opacity
|
||||
});
|
||||
|
||||
self.render_screen_line_opengl(
|
||||
RenderScreenLineOpenGLParams {
|
||||
top_pixel_y: tab_bar_y,
|
||||
left_pixel_x: 0.,
|
||||
pixel_width: self.dimensions.pixel_width as f32,
|
||||
stable_line_idx: None,
|
||||
line: self.tab_bar.line(),
|
||||
selection: 0..0,
|
||||
cursor: &Default::default(),
|
||||
palette: &palette,
|
||||
dims: &RenderableDimensions {
|
||||
cols: self.dimensions.pixel_width
|
||||
/ self.render_metrics.cell_size.width as usize,
|
||||
physical_top: 0,
|
||||
scrollback_rows: 0,
|
||||
scrollback_top: 0,
|
||||
viewport_rows: 1,
|
||||
dpi: self.terminal_size.dpi,
|
||||
pixel_height: self.render_metrics.cell_size.height as usize,
|
||||
pixel_width: self.terminal_size.pixel_width,
|
||||
reverse_video: false,
|
||||
},
|
||||
config: &self.config,
|
||||
cursor_border_color: LinearRgba::default(),
|
||||
foreground: palette.foreground.to_linear(),
|
||||
pane: None,
|
||||
is_active: true,
|
||||
selection_fg: LinearRgba::default(),
|
||||
selection_bg: LinearRgba::default(),
|
||||
cursor_fg: LinearRgba::default(),
|
||||
cursor_bg: LinearRgba::default(),
|
||||
cursor_is_default_color: true,
|
||||
white_space,
|
||||
filled_box,
|
||||
window_is_transparent,
|
||||
default_bg,
|
||||
style: None,
|
||||
font: None,
|
||||
use_pixel_positioning: self.config.experimental_pixel_positioning,
|
||||
render_metrics: self.render_metrics,
|
||||
shape_key: None,
|
||||
password_input: false,
|
||||
},
|
||||
layers,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tab_bar_pixel_height_impl(
|
||||
config: &ConfigHandle,
|
||||
fontconfig: &wezterm_font::FontConfiguration,
|
||||
render_metrics: &RenderMetrics,
|
||||
) -> anyhow::Result<f32> {
|
||||
if config.use_fancy_tab_bar {
|
||||
let font = fontconfig.title_font()?;
|
||||
Ok((font.metrics().cell_height.get() as f32 * 1.75).ceil())
|
||||
} else {
|
||||
Ok(render_metrics.cell_size.height as f32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tab_bar_pixel_height(&self) -> anyhow::Result<f32> {
|
||||
Self::tab_bar_pixel_height_impl(&self.config, &self.fonts, &self.render_metrics)
|
||||
}
|
||||
}
|
355
wezterm-gui/src/termwindow/render/window_buttons.rs
Normal file
355
wezterm-gui/src/termwindow/render/window_buttons.rs
Normal file
@ -0,0 +1,355 @@
|
||||
use crate::customglyph::*;
|
||||
use crate::termwindow::box_model::*;
|
||||
use crate::termwindow::render::corners::*;
|
||||
use crate::termwindow::{TabBarItem, UIItemType};
|
||||
use crate::utilsprites::RenderMetrics;
|
||||
use config::{ConfigHandle, Dimension};
|
||||
use std::rc::Rc;
|
||||
use wezterm_font::LoadedFont;
|
||||
use window::color::LinearRgba;
|
||||
use window::IntegratedTitleButton;
|
||||
|
||||
pub struct WindowButtonColors {
|
||||
pub(super) colors: ElementColors,
|
||||
pub(super) hover_colors: ElementColors,
|
||||
}
|
||||
|
||||
pub(super) fn auto_button_color(
|
||||
background_lightness: f64,
|
||||
foreground: config::IntegratedTitleButtonColor,
|
||||
) -> LinearRgba {
|
||||
use config::IntegratedTitleButtonColor as Color;
|
||||
match foreground {
|
||||
Color::Custom(color) => color.to_linear(),
|
||||
Color::Auto => {
|
||||
if background_lightness > 0.5 {
|
||||
LinearRgba(0.0, 0.0, 0.0, 1.0)
|
||||
} else {
|
||||
LinearRgba(1.0, 1.0, 1.0, 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) mod windows {
|
||||
use super::*;
|
||||
pub const CLOSE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::LineTo(BlockCoord::One, BlockCoord::One),
|
||||
PolyCommand::MoveTo(BlockCoord::One, BlockCoord::Zero),
|
||||
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::One),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::OutlineThin,
|
||||
}];
|
||||
|
||||
pub const HIDE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Frac(6, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::One, BlockCoord::Frac(6, 10)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::OutlineThin,
|
||||
}];
|
||||
|
||||
pub const MAXIMIZE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Frac(2, 10), BlockCoord::Frac(1, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(9, 10), BlockCoord::Frac(1, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(10, 10), BlockCoord::Frac(2, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(10, 10), BlockCoord::Frac(9, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(9, 10), BlockCoord::Frac(10, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(2, 10), BlockCoord::Frac(10, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 10), BlockCoord::Frac(9, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 10), BlockCoord::Frac(2, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(2, 10), BlockCoord::Frac(1, 10)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::OutlineThin,
|
||||
}];
|
||||
|
||||
pub const RESTORE: &[Poly] = &[
|
||||
Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Frac(5, 20), BlockCoord::Frac(1, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(8, 10), BlockCoord::Frac(1, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(10, 10), BlockCoord::Frac(3, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(10, 10), BlockCoord::Frac(15, 20)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::OutlineThin,
|
||||
},
|
||||
Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Frac(2, 10), BlockCoord::Frac(3, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(7, 10), BlockCoord::Frac(3, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(8, 10), BlockCoord::Frac(4, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(8, 10), BlockCoord::Frac(9, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(7, 10), BlockCoord::Frac(10, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(2, 10), BlockCoord::Frac(10, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 10), BlockCoord::Frac(9, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 10), BlockCoord::Frac(4, 10)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(2, 10), BlockCoord::Frac(3, 10)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::OutlineThin,
|
||||
},
|
||||
];
|
||||
|
||||
pub fn sized_poly(poly: &'static [Poly]) -> SizedPoly {
|
||||
let scale = 72.0 / 96.0;
|
||||
let size = Dimension::Points(10. * scale);
|
||||
SizedPoly {
|
||||
poly,
|
||||
width: size,
|
||||
height: size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_button_colors(
|
||||
background_lightness: f64,
|
||||
foreground: config::IntegratedTitleButtonColor,
|
||||
window_button: IntegratedTitleButton,
|
||||
) -> WindowButtonColors {
|
||||
let foreground = auto_button_color(background_lightness, foreground);
|
||||
let colors = ElementColors {
|
||||
border: BorderColor::new(LinearRgba::TRANSPARENT),
|
||||
bg: LinearRgba::TRANSPARENT.into(),
|
||||
text: foreground.into(),
|
||||
};
|
||||
|
||||
let hover_colors = if window_button == IntegratedTitleButton::Close {
|
||||
ElementColors {
|
||||
border: BorderColor::new(LinearRgba::TRANSPARENT),
|
||||
bg: LinearRgba(1.0, 0.0, 0.0, 1.0).into(),
|
||||
text: LinearRgba(1.0, 1.0, 1.0, 1.0).into(),
|
||||
}
|
||||
} else {
|
||||
ElementColors {
|
||||
border: BorderColor::new(LinearRgba::TRANSPARENT),
|
||||
bg: foreground.mul_alpha(0.1).into(),
|
||||
text: foreground.into(),
|
||||
}
|
||||
};
|
||||
|
||||
WindowButtonColors {
|
||||
colors,
|
||||
hover_colors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) mod gnome {
|
||||
use super::*;
|
||||
pub const CLOSE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::LineTo(BlockCoord::One, BlockCoord::One),
|
||||
PolyCommand::MoveTo(BlockCoord::One, BlockCoord::Zero),
|
||||
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::One),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
}];
|
||||
|
||||
pub const HIDE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Frac(15, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::One, BlockCoord::Frac(15, 16)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
}];
|
||||
|
||||
pub const MAXIMIZE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 16), BlockCoord::Frac(15, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(15, 16), BlockCoord::Frac(15, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(15, 16), BlockCoord::Frac(1, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(1, 16), BlockCoord::Frac(1, 16)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
}];
|
||||
|
||||
pub const RESTORE: &[Poly] = &[Poly {
|
||||
path: &[
|
||||
PolyCommand::MoveTo(BlockCoord::Frac(3, 16), BlockCoord::Frac(3, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(3, 16), BlockCoord::Frac(13, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(13, 16), BlockCoord::Frac(13, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(13, 16), BlockCoord::Frac(3, 16)),
|
||||
PolyCommand::LineTo(BlockCoord::Frac(3, 16), BlockCoord::Frac(3, 16)),
|
||||
],
|
||||
intensity: BlockAlpha::Full,
|
||||
style: PolyStyle::Outline,
|
||||
}];
|
||||
|
||||
pub fn sized_poly(poly: &'static [Poly]) -> SizedPoly {
|
||||
let size = Dimension::Pixels(8.);
|
||||
SizedPoly {
|
||||
poly,
|
||||
width: size,
|
||||
height: size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_button_colors(
|
||||
background_lightness: f64,
|
||||
foreground: config::IntegratedTitleButtonColor,
|
||||
_window_button: IntegratedTitleButton,
|
||||
) -> WindowButtonColors {
|
||||
let foreground = auto_button_color(background_lightness, foreground);
|
||||
WindowButtonColors {
|
||||
colors: ElementColors {
|
||||
border: BorderColor::new(foreground.mul_alpha(0.1)),
|
||||
bg: foreground.mul_alpha(0.1).into(),
|
||||
text: foreground.into(),
|
||||
},
|
||||
hover_colors: ElementColors {
|
||||
border: BorderColor::new(foreground.mul_alpha(0.15)),
|
||||
bg: foreground.mul_alpha(0.15).into(),
|
||||
text: foreground.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_button_element(
|
||||
window_button: IntegratedTitleButton,
|
||||
is_maximized: bool,
|
||||
font: &Rc<LoadedFont>,
|
||||
metrics: &RenderMetrics,
|
||||
config: &ConfigHandle,
|
||||
) -> Element {
|
||||
use window::IntegratedTitleButtonStyle as Style;
|
||||
use IntegratedTitleButton as Button;
|
||||
|
||||
let style = config.integrated_title_button_style;
|
||||
|
||||
if style == Style::MacOsNative {
|
||||
return Element::new(font, ElementContent::Text(String::new()));
|
||||
}
|
||||
|
||||
let poly = {
|
||||
let (close, hide, maximize, restore) = match style {
|
||||
Style::Windows => {
|
||||
use self::windows::{CLOSE, HIDE, MAXIMIZE, RESTORE};
|
||||
(CLOSE, HIDE, MAXIMIZE, RESTORE)
|
||||
}
|
||||
Style::Gnome => {
|
||||
use self::gnome::{CLOSE, HIDE, MAXIMIZE, RESTORE};
|
||||
(CLOSE, HIDE, MAXIMIZE, RESTORE)
|
||||
}
|
||||
Style::MacOsNative => unreachable!(),
|
||||
};
|
||||
let poly = match window_button {
|
||||
Button::Hide => hide,
|
||||
Button::Maximize => {
|
||||
if is_maximized {
|
||||
restore
|
||||
} else {
|
||||
maximize
|
||||
}
|
||||
}
|
||||
Button::Close => close,
|
||||
};
|
||||
|
||||
match style {
|
||||
Style::Windows => self::windows::sized_poly(poly),
|
||||
Style::Gnome => self::gnome::sized_poly(poly),
|
||||
Style::MacOsNative => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
let element = Element::new(
|
||||
&font,
|
||||
ElementContent::Poly {
|
||||
line_width: metrics.underline_height.max(2),
|
||||
poly,
|
||||
},
|
||||
);
|
||||
|
||||
let element = match style {
|
||||
Style::Windows => {
|
||||
let left_padding = match window_button {
|
||||
Button::Hide => 17.0,
|
||||
_ => 18.0,
|
||||
};
|
||||
let scale = 72.0 / 96.0;
|
||||
|
||||
element
|
||||
.zindex(1)
|
||||
.vertical_align(VerticalAlign::Middle)
|
||||
.padding(BoxDimension {
|
||||
left: Dimension::Points(left_padding * scale),
|
||||
right: Dimension::Points(18. * scale),
|
||||
top: Dimension::Points(10. * scale),
|
||||
bottom: Dimension::Points(10. * scale),
|
||||
})
|
||||
}
|
||||
Style::Gnome => {
|
||||
let dim = Dimension::Pixels(7.);
|
||||
let border_corners_size = Dimension::Pixels(12.);
|
||||
element
|
||||
.zindex(1)
|
||||
.vertical_align(VerticalAlign::Middle)
|
||||
.padding(BoxDimension {
|
||||
left: dim,
|
||||
right: dim,
|
||||
top: dim,
|
||||
bottom: dim,
|
||||
})
|
||||
.border(BoxDimension::new(Dimension::Pixels(1.)))
|
||||
.border_corners(Some(Corners {
|
||||
top_left: SizedPoly {
|
||||
width: border_corners_size,
|
||||
height: border_corners_size,
|
||||
poly: TOP_LEFT_ROUNDED_CORNER,
|
||||
},
|
||||
top_right: SizedPoly {
|
||||
width: border_corners_size,
|
||||
height: border_corners_size,
|
||||
poly: TOP_RIGHT_ROUNDED_CORNER,
|
||||
},
|
||||
bottom_left: SizedPoly {
|
||||
width: border_corners_size,
|
||||
height: border_corners_size,
|
||||
poly: BOTTOM_LEFT_ROUNDED_CORNER,
|
||||
},
|
||||
bottom_right: SizedPoly {
|
||||
width: border_corners_size,
|
||||
height: border_corners_size,
|
||||
poly: BOTTOM_RIGHT_ROUNDED_CORNER,
|
||||
},
|
||||
}))
|
||||
.margin(BoxDimension {
|
||||
left: dim,
|
||||
right: dim,
|
||||
top: dim,
|
||||
bottom: dim,
|
||||
})
|
||||
}
|
||||
Style::MacOsNative => unreachable!(),
|
||||
};
|
||||
|
||||
let foreground = config.integrated_title_button_color.clone();
|
||||
let background_lightness = {
|
||||
let bg: config::RgbaColor = config.window_frame.active_titlebar_bg.into();
|
||||
let (_h, _s, l, _a) = bg.to_hsla();
|
||||
l
|
||||
};
|
||||
|
||||
let window_button_colors_fn = match style {
|
||||
Style::Windows => self::windows::window_button_colors,
|
||||
Style::Gnome => self::gnome::window_button_colors,
|
||||
Style::MacOsNative => unreachable!(),
|
||||
};
|
||||
|
||||
let colors = window_button_colors_fn(background_lightness, foreground, window_button);
|
||||
|
||||
let element = element
|
||||
.item_type(UIItemType::TabBar(TabBarItem::WindowButton(window_button)))
|
||||
.colors(colors.colors)
|
||||
.hover_colors(Some(colors.hover_colors));
|
||||
|
||||
element
|
||||
}
|
Loading…
Reference in New Issue
Block a user