use crate::core::command::CommandId;
use std::cell::RefCell;
pub const MAX_COMMANDS: usize = 32 * 2048;
thread_local! {
static GLOBAL_COMMAND_SET: RefCell<CommandSet> = RefCell::new(CommandSet::with_all_enabled());
static COMMAND_SET_CHANGED: RefCell<bool> = RefCell::new(false);
}
pub fn command_enabled(command: CommandId) -> bool {
GLOBAL_COMMAND_SET.with(|cs| cs.borrow().has(command))
}
pub fn enable_command(command: CommandId) {
GLOBAL_COMMAND_SET.with(|cs| {
let mut set = cs.borrow_mut();
if !set.has(command) {
COMMAND_SET_CHANGED.with(|changed| *changed.borrow_mut() = true);
}
set.enable_command(command);
});
}
pub fn disable_command(command: CommandId) {
GLOBAL_COMMAND_SET.with(|cs| {
let mut set = cs.borrow_mut();
if set.has(command) {
COMMAND_SET_CHANGED.with(|changed| *changed.borrow_mut() = true);
}
set.disable_command(command);
});
}
pub fn command_set_changed() -> bool {
COMMAND_SET_CHANGED.with(|changed| *changed.borrow())
}
pub fn clear_command_set_changed() {
COMMAND_SET_CHANGED.with(|changed| *changed.borrow_mut() = false);
}
pub fn init_command_set() {
use crate::core::command::{CM_CLOSE, CM_NEXT, CM_PREV, CM_RESIZE, CM_ZOOM};
GLOBAL_COMMAND_SET.with(|cs| {
let mut set = cs.borrow_mut();
*set = CommandSet::with_all_enabled();
for cmd in [CM_CLOSE, CM_ZOOM, CM_RESIZE, CM_NEXT, CM_PREV] {
set.disable_command(cmd);
}
});
COMMAND_SET_CHANGED.with(|changed| *changed.borrow_mut() = false);
}
const COMMANDS_COUNT: usize = MAX_COMMANDS / 32;
#[derive(Clone, PartialEq)]
pub struct CommandSet {
cmds: Box<[u32; COMMANDS_COUNT]>,
}
impl CommandSet {
pub fn new() -> Self {
Self {
cmds: Box::new([0; COMMANDS_COUNT]),
}
}
pub fn with_all_enabled() -> Self {
Self {
cmds: Box::new([0xFFFFFFFF; COMMANDS_COUNT]),
}
}
pub fn has(&self, command: CommandId) -> bool {
let cmd = command as usize;
if cmd >= MAX_COMMANDS {
return true;
}
let word_index = cmd / 32;
let bit_mask = 1u32 << (cmd & 0x1F);
(self.cmds[word_index] & bit_mask) != 0
}
pub fn enable_command(&mut self, command: CommandId) {
let cmd = command as usize;
if cmd >= MAX_COMMANDS {
return;
}
let word_index = cmd / 32;
let bit_mask = 1u32 << (cmd & 0x1F);
self.cmds[word_index] |= bit_mask;
}
pub fn disable_command(&mut self, command: CommandId) {
let cmd = command as usize;
if cmd >= MAX_COMMANDS {
return;
}
let word_index = cmd / 32;
let bit_mask = 1u32 << (cmd & 0x1F);
self.cmds[word_index] &= !bit_mask;
}
pub fn enable_range(&mut self, cmd_start: CommandId, cmd_end: CommandId) {
let start = cmd_start as usize;
let end = cmd_end as usize;
if start >= MAX_COMMANDS || end < start {
return;
}
let end = end.min(MAX_COMMANDS - 1);
let word_start = start / 32;
let word_end = end / 32;
if word_start == word_end {
for bit in (start & 0x1F)..=(end & 0x1F) {
self.cmds[word_start] |= 1u32 << bit;
}
return;
}
for bit in (start & 0x1F)..32 {
self.cmds[word_start] |= 1u32 << bit;
}
for word in (word_start + 1)..word_end {
self.cmds[word] = 0xFFFFFFFF;
}
for bit in 0..=(end & 0x1F) {
self.cmds[word_end] |= 1u32 << bit;
}
}
pub fn disable_range(&mut self, cmd_start: CommandId, cmd_end: CommandId) {
let start = cmd_start as usize;
let end = cmd_end as usize;
if start >= MAX_COMMANDS || end < start {
return;
}
let end = end.min(MAX_COMMANDS - 1);
let word_start = start / 32;
let word_end = end / 32;
if word_start == word_end {
for bit in (start & 0x1F)..=(end & 0x1F) {
self.cmds[word_start] &= !(1u32 << bit);
}
return;
}
for bit in (start & 0x1F)..32 {
self.cmds[word_start] &= !(1u32 << bit);
}
for word in (word_start + 1)..word_end {
self.cmds[word] = 0;
}
for bit in 0..=(end & 0x1F) {
self.cmds[word_end] &= !(1u32 << bit);
}
}
pub fn enable_set(&mut self, other: &CommandSet) {
for i in 0..COMMANDS_COUNT {
self.cmds[i] |= other.cmds[i];
}
}
pub fn disable_set(&mut self, other: &CommandSet) {
for i in 0..COMMANDS_COUNT {
self.cmds[i] &= !other.cmds[i];
}
}
pub fn enable_all(&mut self) {
self.cmds.fill(0xFFFFFFFF);
}
pub fn is_empty(&self) -> bool {
self.cmds.iter().all(|&word| word == 0)
}
pub fn intersect(&mut self, other: &CommandSet) {
for i in 0..COMMANDS_COUNT {
self.cmds[i] &= other.cmds[i];
}
}
pub fn union(&mut self, other: &CommandSet) {
for i in 0..COMMANDS_COUNT {
self.cmds[i] |= other.cmds[i];
}
}
}
impl Default for CommandSet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enable_disable_single() {
let mut cs = CommandSet::new();
assert!(!cs.has(10));
cs.enable_command(10);
assert!(cs.has(10));
cs.disable_command(10);
assert!(!cs.has(10));
}
#[test]
fn test_enable_range() {
let mut cs = CommandSet::new();
cs.enable_range(10, 20);
assert!(!cs.has(9));
assert!(cs.has(10));
assert!(cs.has(15));
assert!(cs.has(20));
assert!(!cs.has(21));
}
#[test]
fn test_enable_all() {
let mut cs = CommandSet::new();
cs.enable_all();
assert!(cs.has(0));
assert!(cs.has(100));
assert!(cs.has(65535));
}
#[test]
fn test_is_empty() {
let mut cs = CommandSet::new();
assert!(cs.is_empty());
cs.enable_command(50);
assert!(!cs.is_empty());
}
#[test]
fn test_commands_default_disabled() {
let cs = CommandSet::new();
assert!(!cs.has(0));
assert!(!cs.has(100));
assert!(!cs.has(1000));
assert!(!cs.has(60000));
assert!(!cs.has(65535)); }
#[test]
fn test_single_command_range_and_clamped_end() {
let mut set = CommandSet::new();
set.enable_range(42, 42);
assert!(set.has(42));
set.disable_range(42, 42);
assert!(!set.has(42));
let mut set = CommandSet::new();
set.enable_range(65530, 65535);
assert!(set.has(65530));
assert!(set.has(65535));
}
#[test]
fn test_init_command_set_disables_window_commands() {
use crate::core::command::{CM_CLOSE, CM_NEXT, CM_PREV, CM_RESIZE, CM_ZOOM};
init_command_set();
for cmd in [CM_CLOSE, CM_ZOOM, CM_RESIZE, CM_NEXT, CM_PREV] {
assert!(!command_enabled(cmd), "cmd {cmd} should start disabled");
}
assert!(command_enabled(crate::core::command::CM_QUIT));
init_command_set();
enable_command(CM_CLOSE);
}
}