use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
macro_rules! flags {
($(#[$outer:meta])* $vis:vis struct $name:ident: $repr:ty { $($(#[$m:meta])* const $flag:ident = $val:expr;)* }) => {
$(#[$outer])*
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
$vis struct $name($repr);
impl $name {
$( $(#[$m])* pub const $flag: $name = $name($val); )*
pub const fn empty() -> Self {
Self(0)
}
pub const fn bits(self) -> $repr {
self.0
}
pub const fn from_bits(bits: $repr) -> Self {
Self(bits)
}
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub const fn intersects(self, other: Self) -> bool {
(self.0 & other.0) != 0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
pub fn set(&mut self, other: Self, on: bool) {
if on {
self.insert(other)
} else {
self.remove(other)
}
}
}
impl core::ops::BitOr for $name {
type Output = Self;
fn bitor(self, r: Self) -> Self {
Self(self.0 | r.0)
}
}
impl core::ops::BitAnd for $name {
type Output = Self;
fn bitand(self, r: Self) -> Self {
Self(self.0 & r.0)
}
}
impl core::ops::BitXor for $name {
type Output = Self;
fn bitxor(self, r: Self) -> Self {
Self(self.0 ^ r.0)
}
}
impl core::ops::BitOrAssign for $name {
fn bitor_assign(&mut self, r: Self) {
self.0 |= r.0;
}
}
impl core::ops::BitAndAssign for $name {
fn bitand_assign(&mut self, r: Self) {
self.0 &= r.0;
}
}
impl core::ops::Not for $name {
type Output = Self;
fn not(self) -> Self {
Self(!self.0)
}
}
impl core::fmt::Debug for $name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}({:#x})", stringify!($name), self.0)
}
}
};
}
pub(crate) use flags;
flags! {
pub struct State: u16 {
const VISIBLE = 0x001;
const CURSOR_VIS = 0x002;
const CURSOR_INS = 0x004;
const SHADOW = 0x008;
const ACTIVE = 0x010;
const SELECTED = 0x020;
const FOCUSED = 0x040;
const DRAGGING = 0x080;
const DISABLED = 0x100;
const MODAL = 0x200;
const DEFAULT = 0x400;
const EXPOSED = 0x800;
const CLOSED = 0x1000;
const RESIZING = 0x2000;
}
}
pub type StateFlags = State;
flags! {
pub struct Options: u16 {
const SELECTABLE = 0x001;
const TOP_SELECT = 0x002;
const FIRST_CLICK = 0x004;
const FRAMED = 0x008;
const PRE_PROCESS = 0x010;
const POST_PROCESS = 0x020;
const BUFFERED = 0x040;
const TILEABLE = 0x080;
const CENTER_X = 0x100;
const CENTER_Y = 0x200;
const CENTERED = 0x300;
const VALIDATE = 0x400;
}
}
flags! {
pub struct Grow: u8 {
const LO_X = 0x01;
const LO_Y = 0x02;
const HI_X = 0x04;
const HI_Y = 0x08;
const ALL = 0x0F;
}
}
pub type GrowFlags = Grow;
macro_rules! deprecated_aliases {
($ty:ident: $($old:ident => $new:ident),* $(,)?) => {
$(
#[deprecated(since = "3.0.0", note = "use the typed flag constant (e.g. `State::MODAL`)")]
pub const $old: $ty = $ty::$new;
)*
};
}
pub(crate) use deprecated_aliases;
deprecated_aliases! { State:
SF_VISIBLE => VISIBLE, SF_CURSOR_VIS => CURSOR_VIS, SF_CURSOR_INS => CURSOR_INS,
SF_SHADOW => SHADOW, SF_ACTIVE => ACTIVE, SF_SELECTED => SELECTED, SF_FOCUSED => FOCUSED,
SF_DRAGGING => DRAGGING, SF_DISABLED => DISABLED, SF_MODAL => MODAL, SF_DEFAULT => DEFAULT,
SF_EXPOSED => EXPOSED, SF_CLOSED => CLOSED, SF_RESIZING => RESIZING,
}
deprecated_aliases! { Options:
OF_SELECTABLE => SELECTABLE, OF_TOP_SELECT => TOP_SELECT, OF_FIRST_CLICK => FIRST_CLICK,
OF_FRAMED => FRAMED, OF_PRE_PROCESS => PRE_PROCESS, OF_POST_PROCESS => POST_PROCESS,
OF_BUFFERED => BUFFERED, OF_TILEABLE => TILEABLE, OF_CENTER_X => CENTER_X,
OF_CENTER_Y => CENTER_Y, OF_CENTERED => CENTERED, OF_VALIDATE => VALIDATE,
}
deprecated_aliases! { Grow:
GF_GROW_LO_X => LO_X, GF_GROW_LO_Y => LO_Y, GF_GROW_HI_X => HI_X, GF_GROW_HI_Y => HI_Y,
GF_GROW_ALL => ALL,
}
static SHADOW_SIZE_CELL: OnceLock<(i16, i16)> = OnceLock::new();
#[inline]
pub fn shadow_size() -> (i16, i16) {
*SHADOW_SIZE_CELL.get_or_init(|| crate::terminal::Terminal::query_cell_aspect_ratio())
}
pub const SHADOW_SIZE: (i16, i16) = (2, 1);
pub const SHADOW_ATTR: u8 = 0x08;
pub const SHADOW_BOTTOM: char = '▄'; pub const SHADOW_SOLID: char = '█'; pub const SHADOW_TOP: char = '▀';
static BLOCK_EDIT_MODE: AtomicBool = AtomicBool::new(false);
#[inline]
pub fn block_edit_mode() -> bool {
BLOCK_EDIT_MODE.load(Ordering::Relaxed)
}
#[inline]
pub fn set_block_edit_mode(on: bool) {
BLOCK_EDIT_MODE.store(on, Ordering::Relaxed);
}
#[inline]
pub fn toggle_block_edit_mode() -> bool {
!BLOCK_EDIT_MODE.fetch_xor(true, Ordering::Relaxed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_flags_are_typed_and_composable() {
let s = State::VISIBLE | State::FOCUSED;
assert!(s.contains(State::FOCUSED));
assert!(!s.contains(State::MODAL));
assert_eq!(s & !State::FOCUSED, State::VISIBLE);
assert_eq!(State::default(), State::empty());
let mut g = Grow::empty();
g.set(Grow::HI_X, true);
assert!(g.intersects(Grow::ALL));
assert_eq!(Options::CENTERED, Options::CENTER_X | Options::CENTER_Y);
}
}