Add a clickable button to capture the screenshot

Allows tablet-, touch- and mouse-only confirmation.
This commit is contained in:
Ivan Molodetskikh
2025-05-09 15:42:23 +03:00
parent fb5c5204e8
commit 0763c7e196
2 changed files with 166 additions and 33 deletions
+18 -6
View File
@@ -2561,8 +2561,12 @@ impl State {
self.niri.queue_redraw_all(); self.niri.queue_redraw_all();
} }
} }
} else if self.niri.screenshot_ui.pointer_up(None) { } else if let Some(capture) = self.niri.screenshot_ui.pointer_up(None) {
self.niri.queue_redraw_all(); if capture {
self.confirm_screenshot(true);
} else {
self.niri.queue_redraw_all();
}
} }
} }
@@ -3129,8 +3133,12 @@ impl State {
} }
} }
TabletToolTipState::Up => { TabletToolTipState::Up => {
if self.niri.screenshot_ui.pointer_up(None) { if let Some(capture) = self.niri.screenshot_ui.pointer_up(None) {
self.niri.queue_redraw_all(); if capture {
self.confirm_screenshot(true);
} else {
self.niri.queue_redraw_all();
}
} }
tool.tip_up(event.time_msec()); tool.tip_up(event.time_msec());
@@ -3639,8 +3647,12 @@ impl State {
}; };
let slot = evt.slot(); let slot = evt.slot();
if self.niri.screenshot_ui.pointer_up(Some(slot)) { if let Some(capture) = self.niri.screenshot_ui.pointer_up(Some(slot)) {
self.niri.queue_redraw_all(); if capture {
self.confirm_screenshot(true);
} else {
self.niri.queue_redraw_all();
}
} }
let serial = SERIAL_COUNTER.next_serial(); let serial = SERIAL_COUNTER.next_serial();
+148 -27
View File
@@ -1,6 +1,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashMap; use std::collections::HashMap;
use std::f64::consts::TAU;
use std::iter::zip; use std::iter::zip;
use std::rc::Rc; use std::rc::Rc;
@@ -18,7 +19,7 @@ use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::backend::renderer::{ExportMem, Texture as _}; use smithay::backend::renderer::{ExportMem, Texture as _};
use smithay::input::keyboard::{Keysym, ModifiersState}; use smithay::input::keyboard::{Keysym, ModifiersState};
use smithay::output::{Output, WeakOutput}; use smithay::output::{Output, WeakOutput};
use smithay::utils::{Physical, Point, Rectangle, Scale, Size, Transform}; use smithay::utils::{Buffer, Physical, Point, Rectangle, Scale, Size, Transform};
use crate::animation::{Animation, Clock}; use crate::animation::{Animation, Clock};
use crate::layout::floating::DIRECTIONAL_MOVE_PX; use crate::layout::floating::DIRECTIONAL_MOVE_PX;
@@ -32,6 +33,7 @@ use crate::utils::to_physical_precise_round;
const SELECTION_BORDER: i32 = 2; const SELECTION_BORDER: i32 = 2;
const PADDING: i32 = 8; const PADDING: i32 = 8;
const RADIUS: i32 = 16;
const FONT: &str = "sans 14px"; const FONT: &str = "sans 14px";
const BORDER: i32 = 4; const BORDER: i32 = 4;
const TEXT_HIDE_P: &str = const TEXT_HIDE_P: &str =
@@ -64,10 +66,13 @@ pub enum ScreenshotUi {
}, },
} }
#[derive(Clone, Copy)] pub enum Button {
enum Button {
Up, Up,
Down { touch_slot: Option<TouchSlot> }, Down {
touch_slot: Option<TouchSlot>,
on_capture_button: bool,
last_pos: (Output, Point<i32, Physical>),
},
} }
pub struct OutputData { pub struct OutputData {
@@ -98,6 +103,16 @@ impl Button {
fn is_down(&self) -> bool { fn is_down(&self) -> bool {
matches!(self, Self::Down { .. }) matches!(self, Self::Down { .. })
} }
fn is_dragging_selection(&self) -> bool {
matches!(
self,
Self::Down {
on_capture_button: false,
..
}
)
}
} }
impl ScreenshotUi { impl ScreenshotUi {
@@ -521,17 +536,15 @@ impl ScreenshotUi {
// The help panel goes on top. // The help panel goes on top.
if let Some((show, hide)) = &output_data.panel { if let Some((show, hide)) = &output_data.panel {
let buffer = if *show_pointer { hide } else { show }; let buffer = if *show_pointer { hide } else { show };
let alpha = if button.is_dragging_selection() {
let size = buffer.texture().size(); 0.3
let padding: i32 = to_physical_precise_round(scale, PADDING); } else {
let x = max(0, (output_data.size.w - size.w) / 2); 0.9
let y = max(0, output_data.size.h - size.h - padding * 2); };
let location = Point::<_, Physical>::from((x, y)) let location = panel_location(output_data, buffer.texture().size())
.to_f64() .to_f64()
.to_logical(scale); .to_logical(scale);
let alpha = if button.is_down() { 0.3 } else { 0.9 };
let elem = PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer( let elem = PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
buffer.clone(), buffer.clone(),
location, location,
@@ -675,7 +688,12 @@ impl ScreenshotUi {
pub fn pointer_motion(&mut self, point: Point<i32, Physical>, slot: Option<TouchSlot>) { pub fn pointer_motion(&mut self, point: Point<i32, Physical>, slot: Option<TouchSlot>) {
let Self::Open { let Self::Open {
selection, selection,
button: Button::Down { touch_slot }, button:
Button::Down {
touch_slot,
on_capture_button,
last_pos,
},
.. ..
} = self } = self
else { else {
@@ -686,6 +704,12 @@ impl ScreenshotUi {
return; return;
} }
last_pos.1 = point;
if *on_capture_button {
return;
}
selection.2 = point; selection.2 = point;
self.update_buffers(); self.update_buffers();
} }
@@ -711,11 +735,30 @@ impl ScreenshotUi {
return false; return false;
} }
if !output_data.contains_key(&output) { let Some(output_data) = output_data.get(&output) else {
return false; return false;
};
if let Some((show, hide)) = &output_data.panel {
let buffer = if *show_pointer { hide } else { show };
let panel_size = buffer.texture().size();
let location = panel_location(output_data, panel_size);
if is_within_capture_button(output_data.scale, panel_size, point - location) {
*button = Button::Down {
touch_slot: slot,
on_capture_button: true,
last_pos: (output, point),
};
return false;
}
} }
*button = Button::Down { touch_slot: slot }; *button = Button::Down {
touch_slot: slot,
on_capture_button: false,
last_pos: (output.clone(), point),
};
*selection = (output, point, point); *selection = (output, point, point);
self.update_buffers(); self.update_buffers();
@@ -723,27 +766,54 @@ impl ScreenshotUi {
true true
} }
pub fn pointer_up(&mut self, slot: Option<TouchSlot>) -> bool { pub fn pointer_up(&mut self, slot: Option<TouchSlot>) -> Option<bool> {
let Self::Open { let Self::Open {
selection, selection,
output_data, output_data,
button, button,
show_pointer,
.. ..
} = self } = self
else { else {
return false; return None;
}; };
let Button::Down { touch_slot } = *button else { let Button::Down {
return false; touch_slot,
on_capture_button,
ref last_pos,
} = *button
else {
return None;
}; };
if touch_slot != slot { if touch_slot != slot {
return false; return None;
} }
let last_pos = last_pos.clone();
*button = Button::Up; *button = Button::Up;
// Check if we released still on the capture button.
if on_capture_button {
let (output, point) = last_pos;
#[allow(clippy::question_mark)]
let Some(output_data) = output_data.get(&output) else {
return None;
};
if let Some((show, hide)) = &output_data.panel {
let buffer = if *show_pointer { hide } else { show };
let panel_size = buffer.texture().size();
let location = panel_location(output_data, panel_size);
if is_within_capture_button(output_data.scale, panel_size, point - location) {
return Some(true);
}
}
}
// Check if the resulting selection is zero-sized, and try to come up with a small // Check if the resulting selection is zero-sized, and try to come up with a small
// default rectangle. // default rectangle.
let (output, a, b) = selection; let (output, a, b) = selection;
@@ -762,7 +832,7 @@ impl ScreenshotUi {
self.update_buffers(); self.update_buffers();
true Some(false)
} }
} }
@@ -853,6 +923,29 @@ pub fn rect_from_corner_points(
Rectangle::from_extremities((x1, y1), (x2 + 1, y2 + 1)) Rectangle::from_extremities((x1, y1), (x2 + 1, y2 + 1))
} }
fn panel_location(output_data: &OutputData, panel_size: Size<i32, Buffer>) -> Point<i32, Physical> {
let scale = output_data.scale;
let padding: i32 = to_physical_precise_round(scale, PADDING);
let x = max(0, (output_data.size.w - panel_size.w) / 2);
let y = max(0, output_data.size.h - panel_size.h - padding * 2);
Point::from((x, y))
}
fn is_within_capture_button(
scale: f64,
panel_size: Size<i32, Buffer>,
pos_within_panel: Point<i32, Physical>,
) -> bool {
let padding: i32 = to_physical_precise_round(scale, PADDING);
let radius = to_physical_precise_round::<i32>(scale, RADIUS) - 2;
let xc = padding + radius;
let yc = panel_size.h / 2;
let pos = pos_within_panel;
(pos.x - xc) * (pos.x - xc) + (pos.y - yc) * (pos.y - yc) <= radius * radius
}
fn render_panel( fn render_panel(
renderer: &mut GlesRenderer, renderer: &mut GlesRenderer,
scale: f64, scale: f64,
@@ -861,6 +954,11 @@ fn render_panel(
let _span = tracy_client::span!("screenshot_ui::render_panel"); let _span = tracy_client::span!("screenshot_ui::render_panel");
let padding: i32 = to_physical_precise_round(scale, PADDING); let padding: i32 = to_physical_precise_round(scale, PADDING);
// Keep the border width even to avoid blurry edges.
let border_width = (f64::from(BORDER) / 2. * scale).round() * 2.;
let half_border_width = (border_width / 2.) as i32;
let radius: i32 = to_physical_precise_round(scale, RADIUS);
let circle_stroke: f64 = to_physical_precise_round(scale, 2.);
// Add 2 px of spacing to separate the backgrounds of the "Space" and "P" keys. // Add 2 px of spacing to separate the backgrounds of the "Space" and "P" keys.
let spacing = to_physical_precise_round::<i32>(scale, 2) * 1024; let spacing = to_physical_precise_round::<i32>(scale, 2) * 1024;
@@ -873,12 +971,14 @@ fn render_panel(
let layout = pangocairo::functions::create_layout(&cr); let layout = pangocairo::functions::create_layout(&cr);
layout.context().set_round_glyph_positions(false); layout.context().set_round_glyph_positions(false);
layout.set_font_description(Some(&font)); layout.set_font_description(Some(&font));
layout.set_alignment(Alignment::Center); layout.set_alignment(Alignment::Left);
layout.set_markup(text); layout.set_markup(text);
layout.set_spacing(spacing); layout.set_spacing(spacing);
let (mut width, mut height) = layout.pixel_size(); let (mut width, mut height) = layout.pixel_size();
width += padding * 2;
width += padding + radius * 2 + padding - half_border_width + padding;
height = max(height, radius * 2);
height += padding * 2; height += padding * 2;
let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?; let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?;
@@ -886,11 +986,33 @@ fn render_panel(
cr.set_source_rgb(0.1, 0.1, 0.1); cr.set_source_rgb(0.1, 0.1, 0.1);
cr.paint()?; cr.paint()?;
cr.move_to(padding.into(), padding.into()); let padding = f64::from(padding);
let half_border_width = f64::from(half_border_width);
let r = f64::from(radius);
let yc = f64::from(height / 2);
cr.new_sub_path();
cr.arc(padding + r, yc, r, 0., TAU);
cr.set_source_rgb(1., 1., 1.);
cr.fill()?;
cr.new_sub_path();
cr.arc(padding + r, yc, r - circle_stroke, 0., TAU);
cr.set_source_rgb(0.1, 0.1, 0.1);
cr.fill()?;
cr.new_sub_path();
cr.arc(padding + r, yc, r - circle_stroke * 2., 0., TAU);
cr.set_source_rgb(1., 1., 1.);
cr.fill()?;
cr.move_to(padding + r * 2. + padding - half_border_width, padding);
let layout = pangocairo::functions::create_layout(&cr); let layout = pangocairo::functions::create_layout(&cr);
layout.context().set_round_glyph_positions(false); layout.context().set_round_glyph_positions(false);
layout.set_font_description(Some(&font)); layout.set_font_description(Some(&font));
layout.set_alignment(Alignment::Center); layout.set_alignment(Alignment::Left);
layout.set_markup(text); layout.set_markup(text);
layout.set_spacing(spacing); layout.set_spacing(spacing);
@@ -903,8 +1025,7 @@ fn render_panel(
cr.line_to(0., height.into()); cr.line_to(0., height.into());
cr.line_to(0., 0.); cr.line_to(0., 0.);
cr.set_source_rgb(0.3, 0.3, 0.3); cr.set_source_rgb(0.3, 0.3, 0.3);
// Keep the border width even to avoid blurry edges. cr.set_line_width(border_width);
cr.set_line_width((f64::from(BORDER) / 2. * scale).round() * 2.);
cr.stroke()?; cr.stroke()?;
drop(cr); drop(cr);