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)
}