1
0
Fork 0
forked from wry/wry

tree: implement pointer constraints

This commit is contained in:
Julian Orth 2022-07-21 20:16:22 +02:00
parent d4c4497043
commit 38d1267ec9
19 changed files with 707 additions and 4 deletions

View file

@ -605,6 +605,12 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_disable_pointer_constraint(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.disable_pointer_constraint();
Ok(())
}
fn handle_set_use_hardware_cursor(
&self,
seat: Seat,
@ -1195,6 +1201,9 @@ impl ConfigProxyHandler {
} => self
.handle_set_use_hardware_cursor(seat, use_hardware_cursor)
.wrn("set_use_hardware_cursor")?,
ClientMessage::DisablePointerConstraint { seat } => self
.handle_disable_pointer_constraint(seat)
.wrn("disable_pointer_constraint")?,
}
Ok(())
}

View file

@ -9,6 +9,8 @@ use std::{
pub struct Fixed(pub i32);
impl Fixed {
pub const EPSILON: Self = Fixed(1);
pub fn is_integer(self) -> bool {
self.0 & 255 == 0
}
@ -86,6 +88,22 @@ impl Add for Fixed {
}
}
impl Sub<i32> for Fixed {
type Output = Self;
fn sub(self, rhs: i32) -> Self::Output {
Self(self.0 - (rhs << 8))
}
}
impl Add<i32> for Fixed {
type Output = Self;
fn add(self, rhs: i32) -> Self::Output {
Self(self.0 + (rhs << 8))
}
}
impl AddAssign for Fixed {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;

View file

@ -14,6 +14,7 @@ use {
wl_output::WlOutputGlobal,
wl_registry::WlRegistry,
wl_seat::{
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1Global,
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1Global, WlSeatGlobal,
},
wl_shm::WlShmGlobal,
@ -147,6 +148,7 @@ impl Globals {
add_singleton!(ExtSessionLockManagerV1Global);
add_singleton!(WpViewporterGlobal);
add_singleton!(WpFractionalScaleManagerV1Global);
add_singleton!(ZwpPointerConstraintsV1Global);
}
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {

View file

@ -4,6 +4,7 @@ mod pointer_owner;
pub mod wl_keyboard;
pub mod wl_pointer;
pub mod wl_touch;
pub mod zwp_pointer_constraints_v1;
pub mod zwp_relative_pointer_manager_v1;
pub mod zwp_relative_pointer_v1;
@ -32,6 +33,7 @@ use {
wl_keyboard::{WlKeyboard, WlKeyboardError, REPEAT_INFO_SINCE},
wl_pointer::WlPointer,
wl_touch::WlTouch,
zwp_pointer_constraints_v1::{SeatConstraint, SeatConstraintStatus},
zwp_relative_pointer_v1::ZwpRelativePointerV1,
},
wl_surface::WlSurface,
@ -152,6 +154,7 @@ pub struct WlSeatGlobal {
changes: NumCell<u32>,
cursor_size: Cell<u32>,
hardware_cursor: Cell<bool>,
constraint: CloneCell<Option<Rc<SeatConstraint>>>,
}
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
@ -205,6 +208,7 @@ impl WlSeatGlobal {
changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE),
cursor_size: Cell::new(DEFAULT_CURSOR_SIZE),
hardware_cursor: Cell::new(state.globals.seats.len() == 0),
constraint: Default::default(),
});
state.add_cursor_size(DEFAULT_CURSOR_SIZE);
let seat = slf.clone();
@ -388,6 +392,47 @@ impl WlSeatGlobal {
.set(Some(self.state.seat_queue.add_last(self.clone())));
}
pub fn disable_pointer_constraint(&self) {
if let Some(constraint) = self.constraint.get() {
constraint.deactivate();
if constraint.status.get() == SeatConstraintStatus::Inactive {
constraint
.status
.set(SeatConstraintStatus::ActivatableOnFocus);
}
}
}
fn maybe_constrain_pointer_node(&self) {
if let Some(pn) = self.pointer_node() {
if let Some(surface) = pn.node_into_surface() {
let (mut x, mut y) = self.pos.get();
let (sx, sy) = surface.buffer_abs_pos.get().position();
x -= Fixed::from_int(sx);
y -= Fixed::from_int(sy);
self.maybe_constrain(&surface, x, y);
}
}
}
fn maybe_constrain(&self, surface: &WlSurface, x: Fixed, y: Fixed) {
if self.constraint.get().is_some() {
return;
}
let candidate = match surface.constraints.get(&self.id) {
Some(c) if c.status.get() == SeatConstraintStatus::Inactive => c,
_ => return,
};
if !candidate.contains(x.round_down(), y.round_down()) {
return;
}
candidate.status.set(SeatConstraintStatus::Active);
if let Some(owner) = candidate.owner.get() {
owner.send_enabled();
}
self.constraint.set(Some(candidate));
}
pub fn set_fullscreen(&self, fullscreen: bool) {
if let Some(tl) = self.keyboard_node.get().node_toplevel() {
tl.tl_set_fullscreen(fullscreen);
@ -800,6 +845,7 @@ impl WlSeatGlobal {
self.queue_link.set(None);
self.tree_changed_handler.set(None);
self.output.set(self.state.dummy_output.get().unwrap());
self.constraint.take();
}
pub fn id(&self) -> SeatId {

View file

@ -19,6 +19,7 @@ use {
AXIS_VALUE120_SINCE_VERSION, POINTER_FRAME_SINCE_VERSION, WHEEL_TILT,
WHEEL_TILT_SINCE_VERSION,
},
zwp_pointer_constraints_v1::{ConstraintType, SeatConstraintStatus},
zwp_relative_pointer_v1::ZwpRelativePointerV1,
Dnd, SeatId, WlSeat, WlSeatGlobal, CHANGE_CURSOR_MOVED,
},
@ -216,6 +217,11 @@ impl WlSeatGlobal {
let pos = output.node.global.pos.get();
x += Fixed::from_int(pos.x1());
y += Fixed::from_int(pos.y1());
if let Some(c) = self.constraint.get() {
if c.ty == ConstraintType::Lock || !c.contains(x.round_down(), y.round_down()) {
c.deactivate();
}
}
self.state.for_each_seat_tester(|t| {
t.send_pointer_abs(self.id, time_usec, x, y);
});
@ -238,9 +244,26 @@ impl WlSeatGlobal {
dx_unaccelerated,
dy_unaccelerated,
);
let constraint = self.constraint.get();
let locked = match &constraint {
Some(c) if c.ty == ConstraintType::Lock => true,
_ => false,
};
let (mut x, mut y) = self.pos.get();
x += dx;
y += dy;
if !locked {
x += dx;
y += dy;
if let Some(c) = &constraint {
let surface_pos = c.surface.buffer_abs_pos.get();
let (x_rel, y_rel) = (x - surface_pos.x1(), y - surface_pos.y1());
let contained = surface_pos.contains(x.round_down(), y.round_down())
&& c.contains(x_rel.round_down(), y_rel.round_down());
if !contained {
let (x_rel, y_rel) = c.warp(x_rel, y_rel);
(x, y) = (x_rel + surface_pos.x1(), y_rel + surface_pos.y1());
}
}
}
self.state.for_each_seat_tester(|t| {
t.send_pointer_rel(
self.id,
@ -604,6 +627,7 @@ impl WlSeatGlobal {
let time = (self.pos_time_usec.get() / 1000) as u32;
self.surface_pointer_event(0, n, |p| p.send_motion(time, x, y));
self.surface_pointer_frame(n);
self.maybe_constrain(n, x, y);
}
}
@ -641,6 +665,12 @@ impl WlSeatGlobal {
n.client.last_enter_serial.set(serial);
self.surface_pointer_event(0, n, |p| p.send_enter(serial, n.id, x, y));
self.surface_pointer_frame(n);
for (_, constraint) in &n.constraints {
if constraint.status.get() == SeatConstraintStatus::ActivatableOnFocus {
constraint.status.set(SeatConstraintStatus::Inactive);
}
}
self.maybe_constrain(n, x, y);
}
}
@ -648,6 +678,9 @@ impl WlSeatGlobal {
impl WlSeatGlobal {
pub fn leave_surface(&self, n: &WlSurface) {
let serial = n.client.next_serial();
for (_, constraint) in &n.constraints {
constraint.deactivate();
}
self.surface_pointer_event(0, n, |p| p.send_leave(serial, n.id));
self.surface_pointer_frame(n);
}

View file

@ -0,0 +1,308 @@
use {
crate::{
client::{Client, ClientError},
fixed::Fixed,
globals::{Global, GlobalName},
ifs::{
wl_seat::{
zwp_pointer_constraints_v1::zwp_confined_pointer_v1::ZwpConfinedPointerV1,
WlSeatGlobal,
},
wl_surface::WlSurface,
},
leaks::Tracker,
object::Object,
rect::Region,
utils::{
buffd::{MsgParser, MsgParserError},
clonecell::CloneCell,
},
wire::{
zwp_pointer_constraints_v1::*, WlPointerId, WlRegionId, WlSurfaceId,
ZwpPointerConstraintsV1Id,
},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
zwp_locked_pointer_v1::ZwpLockedPointerV1,
};
pub mod zwp_confined_pointer_v1;
pub mod zwp_locked_pointer_v1;
pub struct ZwpPointerConstraintsV1Global {
pub name: GlobalName,
}
pub struct ZwpPointerConstraintsV1 {
pub id: ZwpPointerConstraintsV1Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ConstraintType {
Lock,
Confine,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SeatConstraintStatus {
Active,
ActivatableOnFocus,
Inactive,
TerminallyDisabled,
}
pub struct SeatConstraint {
pub owner: CloneCell<Option<Rc<dyn ConstraintOwner>>>,
pub client: Rc<Client>,
pub seat: Rc<WlSeatGlobal>,
pub surface: Rc<WlSurface>,
pub region: CloneCell<Option<Rc<Region>>>,
pub one_shot: bool,
pub status: Cell<SeatConstraintStatus>,
pub ty: ConstraintType,
}
impl SeatConstraint {
pub fn deactivate(&self) {
if self.status.get() == SeatConstraintStatus::Active {
self.seat.constraint.take();
if let Some(owner) = self.owner.get() {
owner.send_disabled();
}
if self.one_shot {
self.status.set(SeatConstraintStatus::TerminallyDisabled);
} else {
self.status.set(SeatConstraintStatus::Inactive);
}
}
}
pub fn contains(&self, x: i32, y: i32) -> bool {
let region = self.region.get();
if let Some(region) = region {
return region.contains(x, y);
}
true
}
pub fn warp(&self, mut x: Fixed, mut y: Fixed) -> (Fixed, Fixed) {
let (x_int, y_int) = (x.round_down(), y.round_down());
let mut best_rect;
if let Some(region) = self.region.get() {
if region.is_empty() {
return (x, y);
}
best_rect = region[0];
let mut best_dist = region[0].dist_squared(x_int, y_int);
for rect in &region[1..] {
let dist = rect.dist_squared(x_int, y_int);
if dist < best_dist {
best_dist = dist;
best_rect = *rect;
}
}
} else {
best_rect = self.surface.buffer_abs_pos.get().at_point(0, 0);
}
if x_int < best_rect.x1() {
x = Fixed::from_int(best_rect.x1());
} else if x_int >= best_rect.x2() {
x = Fixed::from_int(best_rect.x2()) - Fixed::EPSILON;
}
if y_int < best_rect.y1() {
y = Fixed::from_int(best_rect.y1());
} else if y_int >= best_rect.y2() {
y = Fixed::from_int(best_rect.y2()) - Fixed::EPSILON;
}
(x, y)
}
fn detach(&self) {
self.deactivate();
self.owner.take();
self.surface.constraints.remove(&self.seat.id);
}
fn set_region(&self, region: WlRegionId) -> Result<(), ZwpPointerConstraintsV1Error> {
let region = if region.is_some() {
Some(self.client.lookup(region)?.region())
} else {
None
};
self.region.set(region);
Ok(())
}
}
pub trait ConstraintOwner {
fn send_enabled(&self);
fn send_disabled(&self);
}
const LT_ONESHOT: u32 = 1;
const LT_PERSISTENT: u32 = 2;
impl ZwpPointerConstraintsV1Global {
pub fn new(name: GlobalName) -> Self {
Self { name }
}
fn bind_(
self: Rc<Self>,
id: ZwpPointerConstraintsV1Id,
client: &Rc<Client>,
_version: u32,
) -> Result<(), ZwpPointerConstraintsV1Error> {
let cs = Rc::new(ZwpPointerConstraintsV1 {
id,
client: client.clone(),
tracker: Default::default(),
});
track!(client, cs);
client.add_client_obj(&cs)?;
Ok(())
}
}
impl ZwpPointerConstraintsV1 {
fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpPointerConstraintsV1Error> {
let _req: Destroy = self.client.parse(self, msg)?;
self.client.remove_obj(self)?;
Ok(())
}
fn create_constraint(
&self,
ty: ConstraintType,
pointer: WlPointerId,
surface: WlSurfaceId,
region: WlRegionId,
lifetime: u32,
) -> Result<Rc<SeatConstraint>, ZwpPointerConstraintsV1Error> {
let pointer = self.client.lookup(pointer)?;
let seat = &pointer.seat.global;
let surface = self.client.lookup(surface)?;
if surface.constraints.contains(&seat.id) {
return Err(ZwpPointerConstraintsV1Error::AlreadyConstrained);
}
let region = if region.is_some() {
Some(self.client.lookup(region)?.region())
} else {
None
};
let one_shot = match lifetime {
LT_ONESHOT => true,
LT_PERSISTENT => false,
_ => return Err(ZwpPointerConstraintsV1Error::UnknownLifetime(lifetime)),
};
Ok(Rc::new(SeatConstraint {
owner: Default::default(),
client: self.client.clone(),
seat: seat.clone(),
surface,
region: CloneCell::new(region),
one_shot,
status: Cell::new(SeatConstraintStatus::Inactive),
ty,
}))
}
fn lock_pointer(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpPointerConstraintsV1Error> {
let req: LockPointer = self.client.parse(self, msg)?;
let constraint = self.create_constraint(
ConstraintType::Lock,
req.pointer,
req.surface,
req.region,
req.lifetime,
)?;
let lp = Rc::new(ZwpLockedPointerV1 {
id: req.id,
tracker: Default::default(),
constraint,
});
self.client.add_client_obj(&lp)?;
lp.constraint.owner.set(Some(lp.clone()));
lp.constraint
.surface
.constraints
.insert(lp.constraint.seat.id, lp.constraint.clone());
lp.constraint.seat.maybe_constrain_pointer_node();
Ok(())
}
fn confine_pointer(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpPointerConstraintsV1Error> {
let req: ConfinePointer = self.client.parse(self, msg)?;
let constraint = self.create_constraint(
ConstraintType::Confine,
req.pointer,
req.surface,
req.region,
req.lifetime,
)?;
let lp = Rc::new(ZwpConfinedPointerV1 {
id: req.id,
tracker: Default::default(),
constraint,
});
self.client.add_client_obj(&lp)?;
lp.constraint.owner.set(Some(lp.clone()));
lp.constraint
.surface
.constraints
.insert(lp.constraint.seat.id, lp.constraint.clone());
lp.constraint.seat.maybe_constrain_pointer_node();
Ok(())
}
}
global_base!(
ZwpPointerConstraintsV1Global,
ZwpPointerConstraintsV1,
ZwpPointerConstraintsV1Error
);
impl Global for ZwpPointerConstraintsV1Global {
fn singleton(&self) -> bool {
true
}
fn version(&self) -> u32 {
1
}
}
simple_add_global!(ZwpPointerConstraintsV1Global);
object_base! {
ZwpPointerConstraintsV1;
DESTROY => destroy,
LOCK_POINTER => lock_pointer,
CONFINE_POINTER => confine_pointer,
}
impl Object for ZwpPointerConstraintsV1 {
fn num_requests(&self) -> u32 {
CONFINE_POINTER + 1
}
}
simple_add_obj!(ZwpPointerConstraintsV1);
#[derive(Debug, Error)]
pub enum ZwpPointerConstraintsV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error("The surface already has a constraint attached for the seat")]
AlreadyConstrained,
#[error("The constraint lifetime {0} is unknown")]
UnknownLifetime(u32),
}
efrom!(ZwpPointerConstraintsV1Error, ClientError);
efrom!(ZwpPointerConstraintsV1Error, MsgParserError);

View file

@ -0,0 +1,78 @@
use {
crate::{
client::ClientError,
ifs::wl_seat::zwp_pointer_constraints_v1::{
ConstraintOwner, SeatConstraint, ZwpPointerConstraintsV1Error,
},
leaks::Tracker,
object::Object,
utils::buffd::{MsgParser, MsgParserError},
wire::{zwp_confined_pointer_v1::*, ZwpConfinedPointerV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct ZwpConfinedPointerV1 {
pub id: ZwpConfinedPointerV1Id,
pub tracker: Tracker<Self>,
pub constraint: Rc<SeatConstraint>,
}
impl ZwpConfinedPointerV1 {
fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpConfinedPointerV1Error> {
let _req: Destroy = self.constraint.client.parse(self, msg)?;
self.constraint.detach();
self.constraint.client.remove_obj(self)?;
Ok(())
}
fn set_region(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpConfinedPointerV1Error> {
let req: SetRegion = self.constraint.client.parse(self, msg)?;
self.constraint.set_region(req.region)?;
Ok(())
}
}
impl ConstraintOwner for ZwpConfinedPointerV1 {
fn send_enabled(&self) {
self.constraint.client.event(Confined { self_id: self.id });
}
fn send_disabled(&self) {
self.constraint
.client
.event(Unconfined { self_id: self.id });
}
}
object_base! {
ZwpConfinedPointerV1;
DESTROY => destroy,
SET_REGION => set_region,
}
impl Object for ZwpConfinedPointerV1 {
fn num_requests(&self) -> u32 {
SET_REGION + 1
}
fn break_loops(&self) {
self.constraint.detach();
}
}
simple_add_obj!(ZwpConfinedPointerV1);
#[derive(Debug, Error)]
pub enum ZwpConfinedPointerV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error(transparent)]
ZwpPointerConstraintsV1Error(#[from] ZwpPointerConstraintsV1Error),
}
efrom!(ZwpConfinedPointerV1Error, ClientError);
efrom!(ZwpConfinedPointerV1Error, MsgParserError);

View file

@ -0,0 +1,85 @@
use {
crate::{
client::ClientError,
ifs::wl_seat::zwp_pointer_constraints_v1::{
ConstraintOwner, SeatConstraint, ZwpPointerConstraintsV1Error,
},
leaks::Tracker,
object::Object,
utils::buffd::{MsgParser, MsgParserError},
wire::{zwp_locked_pointer_v1::*, ZwpLockedPointerV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct ZwpLockedPointerV1 {
pub id: ZwpLockedPointerV1Id,
pub tracker: Tracker<Self>,
pub constraint: Rc<SeatConstraint>,
}
impl ZwpLockedPointerV1 {
fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpLockedPointerV1Error> {
let _req: Destroy = self.constraint.client.parse(self, msg)?;
self.constraint.detach();
self.constraint.client.remove_obj(self)?;
Ok(())
}
fn set_cursor_position_hint(
&self,
msg: MsgParser<'_, '_>,
) -> Result<(), ZwpLockedPointerV1Error> {
let _req: SetCursorPositionHint = self.constraint.client.parse(self, msg)?;
Ok(())
}
fn set_region(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpLockedPointerV1Error> {
let req: SetRegion = self.constraint.client.parse(self, msg)?;
self.constraint.set_region(req.region)?;
Ok(())
}
}
impl ConstraintOwner for ZwpLockedPointerV1 {
fn send_enabled(&self) {
self.constraint.client.event(Locked { self_id: self.id });
}
fn send_disabled(&self) {
self.constraint.client.event(Unlocked { self_id: self.id });
}
}
object_base! {
ZwpLockedPointerV1;
DESTROY => destroy,
SET_CURSOR_POSITION_HINT => set_cursor_position_hint,
SET_REGION => set_region,
}
impl Object for ZwpLockedPointerV1 {
fn num_requests(&self) -> u32 {
SET_REGION + 1
}
fn break_loops(&self) {
self.constraint.detach();
}
}
simple_add_obj!(ZwpLockedPointerV1);
#[derive(Debug, Error)]
pub enum ZwpLockedPointerV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error(transparent)]
ZwpPointerConstraintsV1Error(#[from] ZwpPointerConstraintsV1Error),
}
efrom!(ZwpLockedPointerV1Error, ClientError);
efrom!(ZwpLockedPointerV1Error, MsgParserError);

View file

@ -20,7 +20,10 @@ use {
TF_180, TF_270, TF_90, TF_FLIPPED, TF_FLIPPED_180, TF_FLIPPED_270, TF_FLIPPED_90,
TF_NORMAL,
},
wl_seat::{wl_pointer::PendingScroll, Dnd, NodeSeatState, SeatId, WlSeatGlobal},
wl_seat::{
wl_pointer::PendingScroll, zwp_pointer_constraints_v1::SeatConstraint, Dnd,
NodeSeatState, SeatId, WlSeatGlobal,
},
wl_surface::{
cursor::CursorSurface, wl_subsurface::WlSubsurface,
wp_fractional_scale_v1::WpFractionalScaleV1, wp_viewport::WpViewport,
@ -253,6 +256,7 @@ pub struct WlSurface {
viewporter: CloneCell<Option<Rc<WpViewport>>>,
output: CloneCell<Rc<OutputNode>>,
fractional_scale: CloneCell<Option<Rc<WpFractionalScaleV1>>>,
pub constraints: SmallMap<SeatId, Rc<SeatConstraint>, 1>,
}
impl Debug for WlSurface {
@ -386,6 +390,7 @@ impl WlSurface {
viewporter: Default::default(),
output: CloneCell::new(client.state.dummy_output.get().unwrap()),
fractional_scale: Default::default(),
constraints: Default::default(),
}
}
@ -595,6 +600,7 @@ impl WlSurface {
self.toplevel.set(None);
self.client.remove_obj(self)?;
self.idle_inhibitors.clear();
self.constraints.take();
Ok(())
}
@ -957,6 +963,9 @@ impl WlSurface {
}
pub fn destroy_node(&self) {
for (_, constraint) in &self.constraints {
constraint.deactivate();
}
for (_, inhibitor) in self.idle_inhibitors.lock().drain() {
inhibitor.deactivate();
}
@ -1020,6 +1029,7 @@ impl Object for WlSurface {
self.presentation_feedback.borrow_mut().clear();
self.viewporter.take();
self.fractional_scale.take();
self.constraints.clear();
}
}

View file

@ -118,6 +118,22 @@ impl Rect {
self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y
}
pub fn dist_squared(&self, x: i32, y: i32) -> i32 {
let mut dx = 0;
if self.raw.x1 > x {
dx = self.raw.x1 - x;
} else if self.raw.x2 < x {
dx = x - self.raw.x1;
}
let mut dy = 0;
if self.raw.y1 > y {
dy = self.raw.y1 - y;
} else if self.raw.y2 < y {
dy = y - self.raw.y1;
}
dx * dx + dy * dy
}
#[allow(dead_code)]
pub fn contains_rect(&self, rect: &Self) -> bool {
self.raw.x1 <= rect.raw.x1

View file

@ -78,6 +78,18 @@ impl Region {
pub fn extents(&self) -> Rect {
self.extents
}
pub fn contains(&self, x: i32, y: i32) -> bool {
if !self.extents.contains(x, y) {
return false;
}
for r in self.deref() {
if r.contains(x, y) {
return true;
}
}
false
}
}
impl Deref for Region {

View file

@ -42,6 +42,10 @@ impl<K: Eq, V, const N: usize> SmallMap<K, V, N> {
}
}
pub fn contains(&self, k: &K) -> bool {
unsafe { self.m.get().deref().contains(k) }
}
pub fn len(&self) -> usize {
unsafe { self.m.get().deref().len() }
}
@ -158,6 +162,15 @@ impl<K: Eq, V, const N: usize> SmallMapMut<K, V, N> {
self.m.len()
}
pub fn contains(&self, k: &K) -> bool {
for (ek, _) in &self.m {
if ek == k {
return true;
}
}
false
}
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
for (ek, ev) in &mut self.m {
if ek == &k {