smithay/src/wayland/compositor/mod.rs

648 lines
23 KiB
Rust
Raw Normal View History

2017-06-11 12:33:03 +00:00
//! Utilities for handling surfaces, subsurfaces and regions
//!
//! This module provides automatic handling of surfaces, subsurfaces
//! and region Wayland objects, by registering an implementation for
//! for the [`wl_compositor`](wayland_server::protocol::wl_compositor)
//! and [`wl_subcompositor`](wayland_server::protocol::wl_subcompositor) globals.
2017-06-11 12:33:03 +00:00
//!
//! ## Why use this implementation
2017-06-11 12:33:03 +00:00
//!
//! This implementation does a simple job: it stores in a coherent way the state of
2017-06-11 12:33:03 +00:00
//! surface trees with subsurfaces, to provide you a direct access to the tree
//! structure and all surface metadata.
//!
//! As such, you can, given a root surface with a role requiring it to be displayed,
//! you can iterate over the whole tree of subsurfaces to recover all the metadata you
//! need to display the subsurface tree.
//!
//! This implementation will not do anything more than present you the metadata specified by the
2017-06-11 12:33:03 +00:00
//! client in a coherent and practical way. All the logic regarding to drawing itself, and
//! the positioning of windows (surface trees) one relative to another is out of its scope.
2017-06-11 12:33:03 +00:00
//!
//! ## How to use it
//!
//! ### Initialization
//!
2021-05-24 17:15:46 +00:00
//! To initialize this implementation, use the [`compositor_init`]
//! method provided by this module. It'll require you to first define as few things, as shown in
//! this example:
2017-06-11 12:33:03 +00:00
//!
//! ```
//! # extern crate wayland_server;
2017-09-03 17:53:29 +00:00
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::compositor::compositor_init;
2017-06-11 12:33:03 +00:00
//!
2017-09-03 17:53:29 +00:00
//! // Declare the roles enum
//! define_roles!(MyRoles);
//!
//! # let mut display = wayland_server::Display::new();
//! // Call the init function:
//! let (token, _, _) = compositor_init::<MyRoles, _, _>(
//! &mut display,
//! |request, surface, compositor_token, dispatch_data| {
//! /*
2018-09-24 22:30:39 +00:00
//! Your handling of the user requests.
//! */
//! },
//! None // put a logger here
2017-06-11 12:33:03 +00:00
//! );
//!
//! // this `token` is what you'll use to access the surface associated data
2017-06-11 12:33:03 +00:00
//!
//! // You're now ready to go!
//! ```
//!
//! ### Use the surface metadata
//!
//! As you can see in the previous example, in the end we are retrieving a token from
//! the `init` function. This token is necessary to retrieve the metadata associated with
2021-05-24 17:15:46 +00:00
//! a surface. It can be cloned. See [`CompositorToken`]
//! for the details of what it enables you.
2017-06-11 12:33:03 +00:00
//!
2021-05-24 17:15:46 +00:00
//! The surface metadata is held in the [`SurfaceAttributes`]
//! struct. In contains double-buffered state pending from the client as defined by the protocol for
//! [`wl_surface`](wayland_server::protocol::wl_surface), as well as your user-defined type holding
//! any data you need to have associated with a struct. See its documentation for details.
2017-09-03 17:53:29 +00:00
//!
2021-05-24 17:15:46 +00:00
//! This [`CompositorToken`] also provides access to the metadata associated with the role of the
//! surfaces. See the documentation of the [`roles`] submodule
//! for a detailed explanation.
2017-06-11 12:33:03 +00:00
use std::{cell::RefCell, fmt, rc::Rc, sync::Mutex};
2017-06-04 15:47:37 +00:00
mod handlers;
2017-09-03 17:53:29 +00:00
pub mod roles;
2018-09-24 22:30:39 +00:00
mod tree;
2017-06-04 15:47:37 +00:00
2017-09-03 17:53:29 +00:00
pub use self::tree::TraversalAction;
2018-10-04 22:37:43 +00:00
use self::{
roles::{Role, RoleType, WrongRole},
tree::SurfaceData,
};
2018-12-15 20:32:28 +00:00
use crate::utils::Rectangle;
2018-10-04 22:37:43 +00:00
use wayland_server::{
protocol::{
wl_buffer, wl_callback, wl_compositor, wl_output, wl_region, wl_subcompositor, wl_surface::WlSurface,
},
DispatchData, Display, Filter, Global, UserDataMap,
2018-09-24 22:30:39 +00:00
};
2017-06-04 15:47:37 +00:00
/// Description of a part of a surface that
2017-06-04 15:47:37 +00:00
/// should be considered damaged and needs to be redrawn
#[derive(Debug)]
2017-06-04 15:47:37 +00:00
pub enum Damage {
/// A rectangle containing the damaged zone, in surface coordinates
Surface(Rectangle),
2017-06-11 20:47:27 +00:00
/// A rectangle containing the damaged zone, in buffer coordinates
2017-06-04 15:47:37 +00:00
///
/// Note: Buffer scaling must be taken into consideration
Buffer(Rectangle),
2017-06-04 15:47:37 +00:00
}
#[derive(Debug, Copy, Clone, Default)]
struct Marker<R> {
_r: ::std::marker::PhantomData<R>,
}
/// New buffer assignation for a surface
#[derive(Debug)]
pub enum BufferAssignment {
/// The surface no longer has a buffer attached to it
Removed,
/// A new buffer has been attached
NewBuffer {
/// The buffer object
buffer: wl_buffer::WlBuffer,
/// location of the new buffer relative to the previous one
delta: (i32, i32),
},
}
/// Data associated with a surface, aggregated by the handlers
2017-06-04 15:47:37 +00:00
///
/// Most of the fields of this struct represent a double-buffered state, which
2021-05-24 17:15:46 +00:00
/// should only be applied once a [`commit`](SurfaceEvent::Commit)
/// request is received from the surface.
2017-06-04 15:47:37 +00:00
///
/// You are responsible for setting those values as you see fit to avoid
/// processing them two times.
pub struct SurfaceAttributes {
2017-06-04 15:47:37 +00:00
/// Buffer defining the contents of the surface
///
/// You are free to set this field to `None` to avoid processing it several
/// times. It'll be set to `Some(...)` if the user attaches a buffer (or `NULL`) to
/// the surface, and be left to `None` if the user does not attach anything.
pub buffer: Option<BufferAssignment>,
2017-06-04 15:47:37 +00:00
/// Scale of the contents of the buffer, for higher-resolution contents.
///
/// If it matches the one of the output displaying this surface, no change
/// is necessary.
pub buffer_scale: i32,
/// Transform under which interpret the contents of the buffer
///
/// If it matches the one of the output displaying this surface, no change
/// is necessary.
pub buffer_transform: wl_output::Transform,
/// Region of the surface that is guaranteed to be opaque
///
/// By default the whole surface is potentially transparent
pub opaque_region: Option<RegionAttributes>,
/// Region of the surface that is sensitive to user input
///
/// By default the whole surface should be sensitive
pub input_region: Option<RegionAttributes>,
/// Damage rectangle
///
/// Hint provided by the client to suggest that only this part
/// of the surface was changed and needs to be redrawn
pub damage: Vec<Damage>,
2021-05-23 09:44:21 +00:00
/// The frame callbacks associated with this surface for the commit
///
2021-05-23 09:44:21 +00:00
/// The server must send the notifications so that a client
/// will not send excessive updates, while still allowing
/// the highest possible update rate for clients that wait for the reply
/// before drawing again. The server should give some time for the client
/// to draw and commit after sending the frame callback events to let it
/// hit the next output refresh.
///
/// A server should avoid signaling the frame callbacks if the
/// surface is not visible in any way, e.g. the surface is off-screen,
/// or completely obscured by other opaque surfaces.
///
/// An example possibility would be to trigger it once the frame
/// associated with this commit has been displayed on the screen.
2021-05-23 09:44:21 +00:00
pub frame_callbacks: Vec<wl_callback::WlCallback>,
2017-06-04 15:47:37 +00:00
/// User-controlled data
///
/// This is your field to host whatever you need.
pub user_data: UserDataMap,
2017-06-04 15:47:37 +00:00
}
2021-02-22 20:05:00 +00:00
// UserDataMap does not implement debug, so we have to impl Debug manually
impl fmt::Debug for SurfaceAttributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SurfaceAttributes")
.field("buffer", &self.buffer)
.field("buffer_scale", &self.buffer_scale)
.field("buffer_transform", &self.buffer_transform)
.field("opaque_region", &self.opaque_region)
.field("input_region", &self.input_region)
.field("damage", &self.damage)
2021-05-23 09:44:21 +00:00
.field("frame_callbacks", &self.frame_callbacks)
.field("user_data", &"...")
.finish()
}
}
impl Default for SurfaceAttributes {
fn default() -> SurfaceAttributes {
2017-06-04 15:47:37 +00:00
SurfaceAttributes {
buffer: None,
buffer_scale: 1,
buffer_transform: wl_output::Transform::Normal,
opaque_region: None,
input_region: None,
damage: Vec::new(),
2021-05-23 09:44:21 +00:00
frame_callbacks: Vec::new(),
user_data: UserDataMap::new(),
2017-06-04 15:47:37 +00:00
}
}
}
/// Attributes defining the behaviour of a sub-surface relative to its parent
2017-09-03 17:53:29 +00:00
#[derive(Copy, Clone, Debug)]
pub struct SubsurfaceRole {
/// Location of the top-left corner of this sub-surface relative to
2017-06-04 15:47:37 +00:00
/// the top-left corner of its parent
pub location: (i32, i32),
2017-06-04 15:47:37 +00:00
/// Sync status of this sub-surface
///
/// If `true`, this surface should be repainted synchronously with its parent
/// if `false`, it should be considered independent of its parent regarding
2017-06-04 15:47:37 +00:00
/// repaint timings.
pub sync: bool,
}
2017-09-03 17:53:29 +00:00
impl Default for SubsurfaceRole {
fn default() -> SubsurfaceRole {
SubsurfaceRole {
location: (0, 0),
2017-06-04 15:47:37 +00:00
sync: true,
}
}
}
/// Kind of a rectangle part of a region
2017-09-03 17:53:29 +00:00
#[derive(Copy, Clone, Debug)]
2017-06-04 15:47:37 +00:00
pub enum RectangleKind {
/// This rectangle should be added to the region
Add,
/// The intersection of this rectangle with the region should
/// be removed from the region
Subtract,
}
/// Description of the contents of a region
///
/// A region is defined as an union and difference of rectangle.
///
/// This struct contains an ordered `Vec` containing the rectangles defining
/// a region. They should be added or subtracted in this order to compute the
2017-06-04 15:47:37 +00:00
/// actual contents of the region.
2017-09-03 17:53:29 +00:00
#[derive(Clone, Debug)]
2017-06-04 15:47:37 +00:00
pub struct RegionAttributes {
/// List of rectangle part of this region
pub rects: Vec<(RectangleKind, Rectangle)>,
}
impl Default for RegionAttributes {
fn default() -> RegionAttributes {
RegionAttributes { rects: Vec::new() }
}
}
impl RegionAttributes {
/// Checks whether given point is inside the region.
pub fn contains(&self, point: (i32, i32)) -> bool {
let mut contains = false;
for (kind, rect) in &self.rects {
if rect.contains(point) {
match kind {
RectangleKind::Add => contains = true,
RectangleKind::Subtract => contains = false,
}
}
}
contains
}
}
2017-06-04 15:47:37 +00:00
/// A Compositor global token
///
/// This token can be cloned at will, and is the entry-point to
/// access data associated with the [`wl_surface`](wayland_server::protocol::wl_surface)
/// and [`wl_region`](wayland_server::protocol::wl_region) managed
2017-06-04 15:47:37 +00:00
/// by the `CompositorGlobal` that provided it.
#[derive(Debug)]
pub struct CompositorToken<R> {
2017-09-03 17:53:29 +00:00
_role: ::std::marker::PhantomData<*mut R>,
2017-06-04 15:47:37 +00:00
}
// we implement them manually because #[derive(..)] would require R: Clone
impl<R> Copy for CompositorToken<R> {}
impl<R> Clone for CompositorToken<R> {
fn clone(&self) -> CompositorToken<R> {
*self
}
}
unsafe impl<R> Send for CompositorToken<R> {}
unsafe impl<R> Sync for CompositorToken<R> {}
impl<R> CompositorToken<R> {
pub(crate) fn make() -> CompositorToken<R> {
CompositorToken {
_role: ::std::marker::PhantomData,
}
}
}
impl<R: 'static> CompositorToken<R> {
2017-06-04 15:47:37 +00:00
/// Access the data of a surface
///
/// The closure will be called with the contents of the data associated with this surface.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-06-04 15:47:37 +00:00
/// will panic (having more than one compositor is not supported).
pub fn with_surface_data<F, T>(self, surface: &WlSurface, f: F) -> T
2017-06-23 13:40:28 +00:00
where
F: FnOnce(&mut SurfaceAttributes) -> T,
2017-06-04 15:47:37 +00:00
{
SurfaceData::<R>::with_data(surface, f)
2017-06-04 15:47:37 +00:00
}
2017-09-03 17:53:29 +00:00
}
2017-06-04 15:47:37 +00:00
impl<R> CompositorToken<R>
2017-09-03 17:53:29 +00:00
where
R: RoleType + Role<SubsurfaceRole> + 'static,
2017-09-03 17:53:29 +00:00
{
/// Access the data of a surface tree from bottom to top
2017-06-04 15:47:37 +00:00
///
/// You provide three closures, a "filter", a "processor" and a "post filter".
2017-06-04 15:47:37 +00:00
///
/// The first closure is initially called on a surface to determine if its children
/// should be processed as well. It returns a `TraversalAction<T>` reflecting that.
///
/// The second closure is supposed to do the actual processing. The processing closure for
/// a surface may be called after the processing closure of some of its children, depending
/// on the stack ordering the client requested. Here the surfaces are processed in the same
/// order as they are supposed to be drawn: from the farthest of the screen to the nearest.
///
/// The third closure is called once all the subtree of a node has been processed, and gives
/// an opportunity for early-stopping. If it returns `true` the processing will continue,
/// while if it returns `false` it'll stop.
///
/// The arguments provided to the closures are, in this order:
2017-09-03 17:53:29 +00:00
///
/// - The surface object itself
/// - a mutable reference to its surface attribute data
/// - a mutable reference to its role data,
/// - a custom value that is passed in a fold-like manner, but only from the output of a parent
/// to its children. See [`TraversalAction`] for details.
2017-09-03 17:53:29 +00:00
///
/// If the surface not managed by the `CompositorGlobal` that provided this token, this
2017-06-04 15:47:37 +00:00
/// will panic (having more than one compositor is not supported).
pub fn with_surface_tree_upward<F1, F2, F3, T>(
self,
surface: &WlSurface,
initial: T,
filter: F1,
processor: F2,
post_filter: F3,
) where
F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T),
F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool,
2017-06-04 15:47:37 +00:00
{
SurfaceData::<R>::map_tree(surface, &initial, filter, processor, post_filter, false);
}
/// Access the data of a surface tree from top to bottom
///
/// Behavior is the same as [`with_surface_tree_upward`](CompositorToken::with_surface_tree_upward), but
/// the processing is done in the reverse order, from the nearest of the screen to the deepest.
///
/// This would typically be used to find out which surface of a subsurface tree has been clicked for example.
pub fn with_surface_tree_downward<F1, F2, F3, T>(
self,
surface: &WlSurface,
initial: T,
filter: F1,
processor: F2,
post_filter: F3,
) where
F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T),
F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool,
{
SurfaceData::<R>::map_tree(surface, &initial, filter, processor, post_filter, true);
2017-06-04 15:47:37 +00:00
}
/// Retrieve the parent of this surface
///
/// Returns `None` is this surface is a root surface
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-06-04 15:47:37 +00:00
/// will panic (having more than one compositor is not supported).
pub fn get_parent(self, surface: &WlSurface) -> Option<WlSurface> {
SurfaceData::<R>::get_parent(surface)
2017-06-04 15:47:37 +00:00
}
/// Retrieve the children of this surface
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn get_children(self, surface: &WlSurface) -> Vec<WlSurface> {
SurfaceData::<R>::get_children(surface)
}
2017-09-03 17:53:29 +00:00
}
impl<R: RoleType + 'static> CompositorToken<R> {
/// Check whether this surface as a role or not
2017-06-04 15:47:37 +00:00
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-06-04 15:47:37 +00:00
/// will panic (having more than one compositor is not supported).
pub fn has_a_role(self, surface: &WlSurface) -> bool {
SurfaceData::<R>::has_a_role(surface)
2017-06-04 15:47:37 +00:00
}
/// Check whether this surface as a specific role
2017-06-04 15:47:37 +00:00
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-09-03 17:53:29 +00:00
/// will panic (having more than one compositor is not supported).
pub fn has_role<RoleData>(self, surface: &WlSurface) -> bool
2017-09-03 17:53:29 +00:00
where
R: Role<RoleData>,
{
SurfaceData::<R>::has_role::<RoleData>(surface)
2017-09-03 17:53:29 +00:00
}
/// Register that this surface has given role with default data
2017-06-04 15:47:37 +00:00
///
/// Fails if the surface already has a role.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-06-04 15:47:37 +00:00
/// will panic (having more than one compositor is not supported).
pub fn give_role<RoleData>(self, surface: &WlSurface) -> Result<(), ()>
2017-09-03 17:53:29 +00:00
where
R: Role<RoleData>,
RoleData: Default,
{
SurfaceData::<R>::give_role::<RoleData>(surface)
2017-06-04 15:47:37 +00:00
}
2017-09-03 17:53:29 +00:00
/// Register that this surface has given role with given data
2017-06-04 15:47:37 +00:00
///
2017-09-03 17:53:29 +00:00
/// Fails if the surface already has a role and returns the data.
2017-06-04 15:47:37 +00:00
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-06-04 15:47:37 +00:00
/// will panic (having more than one compositor is not supported).
pub fn give_role_with<RoleData>(self, surface: &WlSurface, data: RoleData) -> Result<(), RoleData>
2017-09-03 17:53:29 +00:00
where
R: Role<RoleData>,
{
SurfaceData::<R>::give_role_with::<RoleData>(surface, data)
2017-09-03 17:53:29 +00:00
}
2017-09-05 10:05:42 +00:00
/// Access the role data of a surface
///
/// Fails and don't call the closure if the surface doesn't have this role
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-09-05 10:05:42 +00:00
/// will panic (having more than one compositor is not supported).
pub fn with_role_data<RoleData, F, T>(self, surface: &WlSurface, f: F) -> Result<T, WrongRole>
2017-09-05 10:05:42 +00:00
where
R: Role<RoleData>,
2017-09-05 17:51:05 +00:00
F: FnOnce(&mut RoleData) -> T,
2017-09-05 10:05:42 +00:00
{
SurfaceData::<R>::with_role_data::<RoleData, _, _>(surface, f)
2017-09-05 10:05:42 +00:00
}
2017-09-03 17:53:29 +00:00
/// Register that this surface does not have a role any longer and retrieve the data
///
/// Fails if the surface didn't already have this role.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
2017-09-03 17:53:29 +00:00
/// will panic (having more than one compositor is not supported).
pub fn remove_role<RoleData>(self, surface: &WlSurface) -> Result<RoleData, WrongRole>
2017-09-03 17:53:29 +00:00
where
R: Role<RoleData>,
{
SurfaceData::<R>::remove_role::<RoleData>(surface)
2017-06-04 15:47:37 +00:00
}
/// Retrieve the metadata associated with a `wl_region`
///
/// If the region is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn get_region_attributes(self, region: &wl_region::WlRegion) -> RegionAttributes {
match region.as_ref().user_data().get::<Mutex<RegionAttributes>>() {
2018-09-24 22:30:39 +00:00
Some(mutex) => mutex.lock().unwrap().clone(),
None => panic!("Accessing the data of foreign regions is not supported."),
}
}
2017-06-04 15:47:37 +00:00
}
/// Create new [`wl_compositor`](wayland_server::protocol::wl_compositor)
/// and [`wl_subcompositor`](wayland_server::protocol::wl_subcompositor) globals.
2017-06-11 12:33:03 +00:00
///
/// The globals are directly registered into the event loop, and this function
/// returns a [`CompositorToken`] which you'll need access the data associated to
/// the [`wl_surface`](wayland_server::protocol::wl_surface)s.
2017-06-11 12:33:03 +00:00
///
/// It also returns the two global handles, in case you wish to remove these
/// globals from the event loop in the future.
pub fn compositor_init<R, Impl, L>(
display: &mut Display,
implem: Impl,
logger: L,
2017-12-15 17:38:10 +00:00
) -> (
CompositorToken<R>,
Global<wl_compositor::WlCompositor>,
Global<wl_subcompositor::WlSubcompositor>,
2017-12-15 17:38:10 +00:00
)
where
L: Into<Option<::slog::Logger>>,
R: Default + RoleType + Role<SubsurfaceRole> + Send + 'static,
Impl: for<'a> FnMut(SurfaceEvent, WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "compositor_handler"));
let implem = Rc::new(RefCell::new(implem));
let compositor = display.create_global(
4,
Filter::new(move |(new_compositor, _version), _, _| {
self::handlers::implement_compositor::<R, Impl>(new_compositor, log.clone(), implem.clone());
}),
);
let subcompositor = display.create_global(
1,
Filter::new(move |(new_subcompositor, _version), _, _| {
self::handlers::implement_subcompositor::<R>(new_subcompositor);
}),
);
(CompositorToken::make(), compositor, subcompositor)
}
/// User-handled events for surfaces
///
/// The global provided by smithay cannot process these events for you, so
/// they are forwarded directly via your provided implementation, and are
/// described by this global.
#[derive(Debug)]
pub enum SurfaceEvent {
2017-06-11 20:47:27 +00:00
/// The double-buffered state has been validated by the client
///
/// At this point, the pending state that has been accumulated in the [`SurfaceAttributes`] associated
/// to this surface should be atomically integrated into the current state of the surface.
Commit,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn region_attributes_empty() {
let region = RegionAttributes { rects: vec![] };
assert_eq!(region.contains((0, 0)), false);
}
#[test]
fn region_attributes_add() {
let region = RegionAttributes {
rects: vec![(
RectangleKind::Add,
Rectangle {
x: 0,
y: 0,
width: 10,
height: 10,
},
)],
};
assert_eq!(region.contains((0, 0)), true);
}
#[test]
fn region_attributes_add_subtract() {
let region = RegionAttributes {
rects: vec![
(
RectangleKind::Add,
Rectangle {
x: 0,
y: 0,
width: 10,
height: 10,
},
),
(
RectangleKind::Subtract,
Rectangle {
x: 0,
y: 0,
width: 5,
height: 5,
},
),
],
};
assert_eq!(region.contains((0, 0)), false);
assert_eq!(region.contains((5, 5)), true);
}
#[test]
fn region_attributes_add_subtract_add() {
let region = RegionAttributes {
rects: vec![
(
RectangleKind::Add,
Rectangle {
x: 0,
y: 0,
width: 10,
height: 10,
},
),
(
RectangleKind::Subtract,
Rectangle {
x: 0,
y: 0,
width: 5,
height: 5,
},
),
(
RectangleKind::Add,
Rectangle {
x: 2,
y: 2,
width: 2,
height: 2,
},
),
],
};
assert_eq!(region.contains((0, 0)), false);
assert_eq!(region.contains((5, 5)), true);
assert_eq!(region.contains((2, 2)), true);
}
}