use std::cell::RefCell; use std::collections::HashMap; use ordered_float::NotNan; use pangocairo::cairo::{self, ImageSurface}; use pangocairo::pango::{Alignment, FontDescription}; use smithay::backend::renderer::element::Kind; use smithay::output::Output; use smithay::reexports::gbm::Format as Fourcc; use smithay::utils::Transform; use crate::render_helpers::memory::MemoryBuffer; use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement}; use crate::utils::{output_size, to_physical_precise_round}; const TEXT: &str = "Are you sure you want to exit niri?\n\n\ Press Enter to confirm."; const PADDING: i32 = 16; const FONT: &str = "sans 14px"; const BORDER: i32 = 8; pub struct ExitConfirmDialog { is_open: bool, buffers: RefCell, Option>>, } impl ExitConfirmDialog { pub fn new() -> anyhow::Result { Ok(Self { is_open: false, buffers: RefCell::new(HashMap::from([( NotNan::new(1.).unwrap(), Some(render(1.)?), )])), }) } pub fn show(&mut self) -> bool { if !self.is_open { self.is_open = true; true } else { false } } pub fn hide(&mut self) -> bool { if self.is_open { self.is_open = false; true } else { false } } pub fn is_open(&self) -> bool { self.is_open } pub fn render( &self, renderer: &mut R, output: &Output, ) -> Option { if !self.is_open { return None; } let scale = output.current_scale().fractional_scale(); let output_size = output_size(output); let mut buffers = self.buffers.borrow_mut(); let fallback = buffers[&NotNan::new(1.).unwrap()].clone().unwrap(); let buffer = buffers .entry(NotNan::new(scale).unwrap()) .or_insert_with(|| render(scale).ok()); let buffer = buffer.as_ref().unwrap_or(&fallback); let size = buffer.logical_size(); let buffer = TextureBuffer::from_memory_buffer(renderer.as_gles_renderer(), buffer).ok()?; let location = (output_size.to_f64().to_point() - size.to_point()).downscale(2.); let mut location = location.to_physical_precise_round(scale).to_logical(scale); location.x = f64::max(0., location.x); location.y = f64::max(0., location.y); let elem = TextureRenderElement::from_texture_buffer( buffer, location, 1., None, None, Kind::Unspecified, ); Some(PrimaryGpuTextureRenderElement(elem)) } } fn render(scale: f64) -> anyhow::Result { let _span = tracy_client::span!("exit_confirm_dialog::render"); let padding: i32 = to_physical_precise_round(scale, PADDING); let mut font = FontDescription::from_string(FONT); font.set_absolute_size(to_physical_precise_round(scale, font.size())); let surface = ImageSurface::create(cairo::Format::ARgb32, 0, 0)?; let cr = cairo::Context::new(&surface)?; let layout = pangocairo::functions::create_layout(&cr); layout.context().set_round_glyph_positions(false); layout.set_font_description(Some(&font)); layout.set_alignment(Alignment::Center); layout.set_markup(TEXT); let (mut width, mut height) = layout.pixel_size(); width += padding * 2; height += padding * 2; let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?; let cr = cairo::Context::new(&surface)?; cr.set_source_rgb(0.1, 0.1, 0.1); cr.paint()?; cr.move_to(padding.into(), padding.into()); let layout = pangocairo::functions::create_layout(&cr); layout.context().set_round_glyph_positions(false); layout.set_font_description(Some(&font)); layout.set_alignment(Alignment::Center); layout.set_markup(TEXT); cr.set_source_rgb(1., 1., 1.); pangocairo::functions::show_layout(&cr, &layout); cr.move_to(0., 0.); cr.line_to(width.into(), 0.); cr.line_to(width.into(), height.into()); cr.line_to(0., height.into()); cr.line_to(0., 0.); cr.set_source_rgb(1., 0.3, 0.3); // Keep the border width even to avoid blurry edges. cr.set_line_width((f64::from(BORDER) / 2. * scale).round() * 2.); cr.stroke()?; drop(cr); let data = surface.take_data().unwrap(); let buffer = MemoryBuffer::new( data.to_vec(), Fourcc::Argb8888, (width, height), scale, Transform::Normal, ); Ok(buffer) }