libwayshot_xcap/
region.rs

1use std::cmp;
2
3use crate::{
4    WayshotConnection,
5    error::{Error, Result},
6    output::OutputInfo,
7};
8
9pub type FreezeCallback = Box<dyn Fn(&WayshotConnection) -> Result<LogicalRegion>>;
10
11/// Ways to say how a region for a screenshot should be captured.
12pub enum RegionCapturer {
13    /// Capture all of the given outputs.
14    Outputs(Vec<OutputInfo>),
15    /// Capture an already known `LogicalRegion`.
16    Region(LogicalRegion),
17    /// The outputs will be "frozen" to the user at which point the given
18    /// callback is called to get the region to capture. This callback is often
19    /// a user interaction to let the user select a region.
20    Freeze(FreezeCallback),
21}
22
23/// `Region` where the coordinate system is the logical coordinate system used
24/// in Wayland to position outputs. Top left is (0, 0) and any transforms and
25/// scaling have been applied. A unit is a logical pixel, meaning that this is
26/// after scaling has been applied.
27#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
28pub struct LogicalRegion {
29    pub inner: Region,
30}
31
32/// An embedded region is a region entirely inside of another (often an output).
33///
34/// It can only be contained inside of another and cannot exceed its bounds.
35///
36/// Example:
37///
38/// ````ignore
39/// ┌─────────────┐
40/// │             │
41/// │  ┌──────────┼──────┐
42/// │  │          │      ├──► Viewport
43/// │  │          │      │
44/// │  │          ├──────┼─────────────────┐
45/// │  │          │xxxxxx│                 │
46/// │  │          │xxxxx◄├─── Inner region │
47/// │  └──────────┼──────┘                 │
48/// │             │                        │
49/// │             │               Screen 2 ├──► Relative to
50/// │             ├────────────────────────┘
51/// │             │
52/// │    Screen 1 │
53/// └─────────────┘
54/// ````
55#[derive(Debug, Copy, Clone)]
56pub struct EmbeddedRegion {
57    /// The coordinate sysd
58    pub relative_to: LogicalRegion,
59    pub inner: Region,
60}
61
62/// Rectangle area in an unspecified coordinate system.
63///
64/// Use `LogicalRegion` or `EmbeddedRegion` instead as they convey the
65/// coordinate system used.
66#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
67pub struct Region {
68    /// Position of the region.
69    pub position: Position,
70    /// Size of the region.
71    pub size: Size,
72}
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
75pub struct Position {
76    /// X coordinate.
77    pub x: i32,
78    /// Y coordinate.
79    pub y: i32,
80}
81
82#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
83pub struct Size {
84    /// Width.
85    pub width: u32,
86    /// Height.
87    pub height: u32,
88}
89
90impl EmbeddedRegion {
91    /// Given two `LogicalRegion`s, one seen as the `viewport` and the other
92    /// `relative_to` (think the output we want to capture), create an
93    /// embedded region that is entirely inside of the `relative_to` region.
94    ///
95    /// See `EmbeddedRegion` for an example ASCII visualisation.
96    #[tracing::instrument(ret, level = "debug")]
97    pub fn new(viewport: LogicalRegion, relative_to: LogicalRegion) -> Option<Self> {
98        let x_relative: i32 = viewport.inner.position.x - relative_to.inner.position.x;
99        let y_relative = viewport.inner.position.y - relative_to.inner.position.y;
100
101        let x1 = cmp::max(x_relative, 0);
102        let x2 = cmp::min(
103            x_relative + viewport.inner.size.width as i32,
104            relative_to.inner.size.width as i32,
105        );
106        let width = if let Ok(width) = (x2 - x1).try_into() {
107            width
108        } else {
109            return None;
110        };
111
112        let y1 = cmp::max(y_relative, 0);
113        let y2 = cmp::min(
114            y_relative + viewport.inner.size.height as i32,
115            relative_to.inner.size.height as i32,
116        );
117        let height = if let Ok(height) = (y2 - y1).try_into() {
118            height
119        } else {
120            return None;
121        };
122
123        Some(Self {
124            relative_to,
125            inner: Region {
126                position: Position { x: x1, y: y1 },
127                size: Size { width, height },
128            },
129        })
130    }
131
132    /// Return the `LogicalRegion` of the embedded region.
133    ///
134    /// Note that this remains a region of the same size, it's not the inverse
135    /// of `EmbeddedRegion::new` which removes the parts that are outside of
136    /// the `relative_to` region.
137    pub fn logical(&self) -> LogicalRegion {
138        LogicalRegion {
139            inner: Region {
140                position: Position {
141                    x: self.relative_to.inner.position.x + self.inner.position.x,
142                    y: self.relative_to.inner.position.y + self.inner.position.y,
143                },
144                size: self.inner.size,
145            },
146        }
147    }
148}
149
150impl std::fmt::Display for EmbeddedRegion {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        write!(
153            f,
154            "{region} relative to {relative_to}",
155            region = self.inner,
156            relative_to = self.relative_to,
157        )
158    }
159}
160
161impl std::fmt::Display for Position {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(f, "({x}, {y})", x = self.x, y = self.y,)
164    }
165}
166
167impl std::fmt::Display for Size {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(
170            f,
171            "({width}x{height})",
172            width = self.width,
173            height = self.height,
174        )
175    }
176}
177
178impl std::fmt::Display for Region {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        write!(
181            f,
182            "{position} {size}",
183            position = self.position,
184            size = self.size,
185        )
186    }
187}
188
189impl std::fmt::Display for LogicalRegion {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        write!(f, "{inner}", inner = self.inner)
192    }
193}
194
195impl From<&OutputInfo> for LogicalRegion {
196    fn from(output_info: &OutputInfo) -> Self {
197        LogicalRegion {
198            inner: output_info.logical_region.inner,
199        }
200    }
201}
202
203impl TryFrom<&[OutputInfo]> for LogicalRegion {
204    type Error = Error;
205
206    fn try_from(output_info: &[OutputInfo]) -> std::result::Result<Self, Self::Error> {
207        let x1 = output_info
208            .iter()
209            .map(|output| output.logical_region.inner.position.x)
210            .min()
211            .ok_or(Error::NoOutputs)?;
212        let y1 = output_info
213            .iter()
214            .map(|output| output.logical_region.inner.position.y)
215            .min()
216            .ok_or(Error::NoOutputs)?;
217        let x2 = output_info
218            .iter()
219            .map(|output| {
220                output.logical_region.inner.position.x
221                    + output.logical_region.inner.size.width as i32
222            })
223            .max()
224            .ok_or(Error::NoOutputs)?;
225        let y2 = output_info
226            .iter()
227            .map(|output| {
228                output.logical_region.inner.position.y
229                    + output.logical_region.inner.size.height as i32
230            })
231            .max()
232            .ok_or(Error::NoOutputs)?;
233        Ok(LogicalRegion {
234            inner: Region {
235                position: Position { x: x1, y: y1 },
236                size: Size {
237                    width: (x2 - x1) as u32,
238                    height: (y2 - y1) as u32,
239                },
240            },
241        })
242    }
243}