use std::cell::RefCell;
use std::cmp::{max, min};
use std::collections::HashMap;
use std::iter::zip;
use std::rc::Rc;
use anyhow::Context;
use arrayvec::ArrayVec;
use niri_config::{Action, Config};
use niri_ipc::SizeChange;
use pango::{Alignment, FontDescription};
use pangocairo::cairo::{self, ImageSurface};
use smithay::backend::allocator::Fourcc;
use smithay::backend::input::TouchSlot;
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
use smithay::backend::renderer::element::Kind;
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::backend::renderer::{ExportMem, Texture as _};
use smithay::input::keyboard::{Keysym, ModifiersState};
use smithay::output::{Output, WeakOutput};
use smithay::utils::{Physical, Point, Rectangle, Scale, Size, Transform};
use crate::animation::{Animation, Clock};
use crate::layout::floating::DIRECTIONAL_MOVE_PX;
use crate::niri_render_elements;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
use crate::render_helpers::{render_to_texture, RenderTarget};
use crate::utils::to_physical_precise_round;
const SELECTION_BORDER: i32 = 2;
const PADDING: i32 = 8;
const FONT: &str = "sans 14px";
const BORDER: i32 = 4;
const TEXT_HIDE_P: &str =
"Press <span face='mono' bgcolor='#2C2C2C'> Space </span> to save the screenshot.\n\
Press <span face='mono' bgcolor='#2C2C2C'> P </span> to hide the pointer.";
const TEXT_SHOW_P: &str =
"Press <span face='mono' bgcolor='#2C2C2C'> Space </span> to save the screenshot.\n\
Press <span face='mono' bgcolor='#2C2C2C'> P </span> to show the pointer.";
// Ideally the screenshot UI should support cross-output selections. However, that poses some
// technical challenges when the outputs have different scales and such. So, this implementation
// allows only single-output selections for now.
//
// As a consequence of this, selection coordinates are in output-local coordinate space.
#[allow(clippy::large_enum_variant)]
pub enum ScreenshotUi {
Closed {
last_selection: Option<(WeakOutput, Rectangle<i32, Physical>)>,
clock: Clock,
config: Rc<RefCell<Config>>,
},
Open {
selection: (Output, Point<i32, Physical>, Point<i32, Physical>),
output_data: HashMap<Output, OutputData>,
mouse_down: bool,
touch_slot: Option<TouchSlot>,
show_pointer: bool,
open_anim: Animation,
clock: Clock,
config: Rc<RefCell<Config>>,
},
}
pub struct OutputData {
size: Size<i32, Physical>,
scale: f64,
transform: Transform,
// Output, screencast, screen capture.
screenshot: [OutputScreenshot; 3],
buffers: [SolidColorBuffer; 8],
locations: [Point<i32, Physical>; 8],
panel: Option<(TextureBuffer<GlesTexture>, TextureBuffer<GlesTexture>)>,
}
pub struct OutputScreenshot {
texture: GlesTexture,
buffer: PrimaryGpuTextureRenderElement,
pointer: Option<PrimaryGpuTextureRenderElement>,
}
niri_render_elements! {
ScreenshotUiRenderElement => {
Screenshot = PrimaryGpuTextureRenderElement,
SolidColor = SolidColorRenderElement,
}
}
impl ScreenshotUi {
pub fn new(clock: Clock, config: Rc<RefCell<Config>>) -> Self {
Self::Closed {
last_selection: None,
clock,
config,
}
}
pub fn open(
&mut self,
renderer: &mut GlesRenderer,
// Output, screencast, screen capture.
screenshots: HashMap<Output, [OutputScreenshot; 3]>,
default_output: Output,
show_pointer: bool,
) -> bool {
if screenshots.is_empty() {
return false;
}
let Self::Closed {
last_selection,
clock,
config,
} = self
else {
return false;
};
let last_selection = last_selection
.take()
.and_then(|(weak, sel)| weak.upgrade().map(|output| (output, sel)));
let selection = match last_selection {
Some(selection) if screenshots.contains_key(&selection.0) => selection,
_ => {
let output = default_output;
let output_transform =