use std::sync::atomic::{AtomicU64, Ordering};
use dioxus::prelude::*;
use crate::core::types::Point;
static NEXT_WINDOW_KEY: AtomicU64 = AtomicU64::new(1);
static NEXT_FOCUS_STAMP: AtomicU64 = AtomicU64::new(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WindowKey(pub u64);
impl WindowKey {
pub fn auto() -> Self {
Self(NEXT_WINDOW_KEY.fetch_add(1, Ordering::Relaxed))
}
}
pub(crate) fn client_to_global(client: Point, origin: Point, scale: f64) -> Point {
Point::new(origin.x + client.x * scale, origin.y + client.y * scale)
}
pub(crate) fn global_to_client(global: Point, origin: Point, scale: f64) -> Point {
let s = if scale > 0.0 { scale } else { 1.0 };
Point::new((global.x - origin.x) / s, (global.y - origin.y) / s)
}
pub(crate) fn window_contains(global: Point, origin: Point, size: (f64, f64)) -> bool {
global.x >= origin.x
&& global.x <= origin.x + size.0
&& global.y >= origin.y
&& global.y <= origin.y + size.1
}
pub struct WindowGeometry {
origin: Signal<Option<Point>>,
size: Signal<Option<(f64, f64)>>,
scale: Signal<f64>,
focused: Signal<u64>,
eligible: Signal<bool>,
}
impl Copy for WindowGeometry {}
impl Clone for WindowGeometry {
fn clone(&self) -> Self {
*self
}
}
impl PartialEq for WindowGeometry {
fn eq(&self, other: &Self) -> bool {
self.origin == other.origin
}
}
impl Default for WindowGeometry {
fn default() -> Self {
Self::new()
}
}
impl WindowGeometry {
pub fn new() -> Self {
Self {
origin: Signal::new(None),
size: Signal::new(None),
scale: Signal::new(1.0),
focused: Signal::new(0),
eligible: Signal::new(true),
}
}
pub fn set(&self, origin: Point, size: (f64, f64), scale: f64) {
let (mut o, mut sz, mut sc) = (self.origin, self.size, self.scale);
if matches!(o.try_peek().as_deref(), Ok(v) if *v != Some(origin)) {
if let Ok(mut w) = o.try_write() {
*w = Some(origin);
}
}
if matches!(sz.try_peek().as_deref(), Ok(v) if *v != Some(size)) {
if let Ok(mut w) = sz.try_write() {
*w = Some(size);
}
}
if matches!(sc.try_peek().as_deref(), Ok(v) if *v != scale) {
if let Ok(mut w) = sc.try_write() {
*w = scale;
}
}
}
pub fn clear(&self) {
let (mut o, mut sz) = (self.origin, self.size);
if matches!(o.try_peek().as_deref(), Ok(Some(_))) {
if let Ok(mut w) = o.try_write() {
*w = None;
}
}
if matches!(sz.try_peek().as_deref(), Ok(Some(_))) {
if let Ok(mut w) = sz.try_write() {
*w = None;
}
}
}
pub fn set_eligible(&self, eligible: bool) {
let mut value = self.eligible;
if matches!(value.try_peek().as_deref(), Ok(current) if *current != eligible) {
if let Ok(mut writer) = value.try_write() {
*writer = eligible;
}
}
}
pub fn eligible(&self) -> bool {
self.eligible
.try_read()
.map(|value| *value)
.unwrap_or(false)
}
pub fn mark_focused(&self) {
let mut f = self.focused;
if let Ok(mut w) = f.try_write() {
*w = NEXT_FOCUS_STAMP.fetch_add(1, Ordering::Relaxed);
};
}
pub fn live(&self) -> bool {
let has_origin = matches!(self.origin.try_read().as_deref(), Ok(Some(_)));
let has_size = matches!(self.size.try_read().as_deref(), Ok(Some(_)));
let eligible = self
.eligible
.try_read()
.map(|value| *value)
.unwrap_or(false);
has_origin && has_size && eligible
}
fn origin_scale(&self) -> Option<(Point, f64)> {
let origin = (*self.origin.try_peek().ok()?)?;
let scale = self.scale.try_peek().map(|s| *s).unwrap_or(1.0);
Some((origin, scale))
}
pub fn to_global(&self, client: Point) -> Option<Point> {
let (origin, scale) = self.origin_scale()?;
Some(client_to_global(client, origin, scale))
}
pub fn to_client(&self, global: Point) -> Option<Point> {
let (origin, scale) = self.origin_scale()?;
Some(global_to_client(global, origin, scale))
}
pub fn contains_global(&self, global: Point) -> bool {
if !self
.eligible
.try_peek()
.map(|eligible| *eligible)
.unwrap_or(false)
{
return false;
}
let origin = self.origin.try_peek().ok().and_then(|o| *o);
let size = self.size.try_peek().ok().and_then(|s| *s);
match (origin, size) {
(Some(origin), Some(size)) => window_contains(global, origin, size),
_ => false,
}
}
pub fn scale(&self) -> f64 {
self.scale.try_peek().map(|s| *s).unwrap_or(1.0)
}
pub fn focus_stamp(&self) -> u64 {
self.focused.try_peek().map(|f| *f).unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conversions_round_trip_under_mixed_scales() {
for scale in [1.0, 1.5, 2.0] {
let origin = Point::new(1200.0, 300.0);
let client = Point::new(80.0, 40.5);
let global = client_to_global(client, origin, scale);
assert_eq!(
global,
Point::new(1200.0 + 80.0 * scale, 300.0 + 40.5 * scale)
);
let back = global_to_client(global, origin, scale);
assert!((back.x - client.x).abs() < 1e-9);
assert!((back.y - client.y).abs() < 1e-9);
}
}
#[test]
fn degenerate_scale_does_not_divide_by_zero() {
let p = global_to_client(Point::new(10.0, 10.0), Point::new(0.0, 0.0), 0.0);
assert_eq!(p, Point::new(10.0, 10.0));
}
#[test]
fn window_containment_is_edge_inclusive() {
let origin = Point::new(100.0, 100.0);
let size = (800.0, 600.0);
assert!(window_contains(Point::new(100.0, 100.0), origin, size));
assert!(window_contains(Point::new(900.0, 700.0), origin, size));
assert!(!window_contains(Point::new(99.9, 100.0), origin, size));
assert!(!window_contains(Point::new(901.0, 300.0), origin, size));
}
#[test]
fn window_keys_are_unique() {
let a = WindowKey::auto();
let b = WindowKey::auto();
assert_ne!(a, b);
}
}