1use crate::types::cpt_ids::{EzCptId, EzCptIds};
2use crate::types::event::EzMsg;
3use crate::types::ship_ids::EzShipId;
4use crate::{AttrValue, Attribute, EzEvent, EzShipIds};
5use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
6use std::fmt::Debug;
7use std::ops::Range;
8
9#[derive(Debug, PartialEq)]
12pub enum SubId<CID, SID>
13where
14 CID: EzCptIds,
15 SID: EzShipIds,
16{
17 CPt(EzCptId<CID>),
19 Ship(EzShipId<SID>),
21}
22
23#[derive(Debug)]
29pub struct Subscription<CID, SID, CM>
30where
31 CID: EzCptIds,
32 SID: EzShipIds,
33 CM: EzMsg,
34{
35 target: SubId<CID, SID>,
36 ev: EventClause<CID, CM>,
37 when: SubClause<CID, SID>,
38}
39
40impl<CID, SID, CM> Subscription<CID, SID, CM>
41where
42 CID: EzCptIds,
43 SID: EzShipIds,
44 CM: EzMsg,
45{
46 pub fn new(
48 target: SubId<CID, SID>,
49 ev: EventClause<CID, CM>,
50 when: SubClause<CID, SID>,
51 ) -> Self {
52 Self { target, ev, when }
53 }
54
55 pub(crate) fn target(&self) -> &SubId<CID, SID> {
56 &self.target
57 }
58
59 pub(crate) fn event(&self) -> &EventClause<CID, CM> {
60 &self.ev
61 }
62
63 pub(crate) fn forward<QueryFn, IsActiveFn>(
64 &self,
65 ev: &EzEvent<CID, CM>,
66 query_fn: QueryFn,
67 is_active_fn: IsActiveFn,
68 ) -> bool
69 where
70 QueryFn: Fn(&SubId<CID, SID>, Attribute) -> Option<AttrValue>,
71 IsActiveFn: Fn(&SubId<CID, SID>) -> bool,
72 {
73 self.ev.forward(ev) & self.when.forward(query_fn, is_active_fn)
74 }
75}
76
77#[derive(Debug, PartialEq, Eq)]
79pub struct MouseEventClause {
80 pub kind: MouseEventKind,
82 pub modifiers: KeyModifiers,
84 pub column: Range<u16>,
86 pub row: Range<u16>,
88}
89
90impl MouseEventClause {
91 fn is_in_range(&self, ev: MouseEvent) -> bool {
92 self.column.contains(&ev.column) && self.row.contains(&ev.row)
93 }
94}
95
96#[derive(Debug, PartialEq, Eq)]
97pub enum EventClause<CID, CM>
100where
101 CID: EzCptIds,
102 CM: EzMsg,
103{
104 Any,
106 Keyboard(KeyEvent),
108 Mouse(MouseEventClause),
110 WindowResize,
112 Init,
114 Lifecycle,
116 AnyClient,
118 Client(CM),
120
121 Specific(EzEvent<CID, CM>),
123}
124
125impl<CID, CM> EventClause<CID, CM>
126where
127 CID: EzCptIds,
128 CM: EzMsg,
129{
130 fn forward(&self, ev: &EzEvent<CID, CM>) -> bool {
131 match self {
132 EventClause::<CID, CM>::Any => true,
133 EventClause::<CID, CM>::Keyboard(_) => ev.as_kb().is_some(),
134 EventClause::<CID, CM>::Mouse(m) => ev.as_mouse().is_some_and(|ev| m.is_in_range(*ev)),
135 EventClause::<CID, CM>::WindowResize => ev.as_resize().is_some(),
136 EventClause::<CID, CM>::Init => ev == &EzEvent::Init,
137 EventClause::<CID, CM>::Lifecycle => ev == &EzEvent::Render || ev == &EzEvent::Tick,
138 EventClause::<CID, CM>::AnyClient => ev.as_client().is_some(),
139 EventClause::<CID, CM>::Client(u) => Some(u) == ev.as_client(),
140 EventClause::<CID, CM>::Specific(e) => ev == e,
141 }
142 }
143}
144
145#[derive(Debug, PartialEq)]
147pub enum SubClause<CID, SID>
148where
149 CID: EzCptIds,
150 SID: EzShipIds,
151{
152 Always,
154 HasAttrValue(SubId<CID, SID>, Attribute, AttrValue),
156 IsActive(SubId<CID, SID>),
158 Not(Box<SubClause<CID, SID>>),
160 And(Box<SubClause<CID, SID>>, Box<SubClause<CID, SID>>),
162 Or(Box<SubClause<CID, SID>>, Box<SubClause<CID, SID>>),
164 Xor(Box<SubClause<CID, SID>>, Box<SubClause<CID, SID>>),
166}
167
168impl<CID, SID> SubClause<CID, SID>
169where
170 CID: EzCptIds,
171 SID: EzShipIds,
172{
173 #[allow(clippy::should_implement_trait)]
175 pub fn not(clause: Self) -> Self {
176 Self::Not(Box::new(clause))
177 }
178
179 pub fn and(a: Self, b: Self) -> Self {
181 Self::And(Box::new(a), Box::new(b))
182 }
183
184 pub fn or(a: Self, b: Self) -> Self {
186 Self::Or(Box::new(a), Box::new(b))
187 }
188
189 pub fn xor(a: Self, b: Self) -> Self {
191 Self::Xor(Box::new(a), Box::new(b))
192 }
193
194 pub(crate) fn forward<QueryFn, IsActiveFn>(
195 &self,
196 query_fn: QueryFn,
197 is_active_fn: IsActiveFn,
198 ) -> bool
199 where
200 QueryFn: Fn(&SubId<CID, SID>, Attribute) -> Option<AttrValue>,
201 IsActiveFn: Fn(&SubId<CID, SID>) -> bool,
202 {
203 self.check_forwarding(query_fn, is_active_fn).0
204 }
205
206 fn check_forwarding<QueryFn, IsActiveFn>(
207 &self,
208 query_fn: QueryFn,
209 is_active_fn: IsActiveFn,
210 ) -> (bool, QueryFn, IsActiveFn)
211 where
212 QueryFn: Fn(&SubId<CID, SID>, Attribute) -> Option<AttrValue>,
213 IsActiveFn: Fn(&SubId<CID, SID>) -> bool,
214 {
215 match self {
216 Self::Always => (true, query_fn, is_active_fn),
217 Self::HasAttrValue(id, query, value) => {
218 let (fwd, query_fn) = Self::has_attribute(id, query, value, query_fn);
219 (fwd, query_fn, is_active_fn)
220 }
221 Self::IsActive(id) => {
222 let (fwd, is_active_fn) = Self::is_mounted(id, is_active_fn);
223 (fwd, query_fn, is_active_fn)
224 }
225 Self::Not(clause) => {
226 let (fwd, query_fn, is_active_fn) = clause.check_forwarding(query_fn, is_active_fn);
227 (!fwd, query_fn, is_active_fn)
228 }
229 Self::And(a, b) => {
230 let (fwd_a, query_fn, is_active_fn) = a.check_forwarding(query_fn, is_active_fn);
231 let (fwd_b, query_fn, is_active_fn) = b.check_forwarding(query_fn, is_active_fn);
232 (fwd_a && fwd_b, query_fn, is_active_fn)
233 }
234 Self::Or(a, b) => {
235 let (fwd_a, query_fn, is_active_fn) = a.check_forwarding(query_fn, is_active_fn);
236 let (fwd_b, query_fn, is_active_fn) = b.check_forwarding(query_fn, is_active_fn);
237 (fwd_a || fwd_b, query_fn, is_active_fn)
238 }
239 Self::Xor(a, b) => {
240 let (fwd_a, query_fn, is_active_fn) = a.check_forwarding(query_fn, is_active_fn);
241 let (fwd_b, query_fn, is_active_fn) = b.check_forwarding(query_fn, is_active_fn);
242 (fwd_a ^ fwd_b, query_fn, is_active_fn)
243 }
244 }
245 }
246
247 fn has_attribute<QueryFn>(
248 id: &SubId<CID, SID>,
249 query: &Attribute,
250 value: &AttrValue,
251 query_fn: QueryFn,
252 ) -> (bool, QueryFn)
253 where
254 QueryFn: Fn(&SubId<CID, SID>, Attribute) -> Option<AttrValue>,
255 {
256 (
257 match query_fn(id, *query) {
258 None => false,
259 Some(v) => *value == v,
260 },
261 query_fn,
262 )
263 }
264
265 fn is_mounted<IsActiveFn>(id: &SubId<CID, SID>, mounted_fn: IsActiveFn) -> (bool, IsActiveFn)
266 where
267 IsActiveFn: Fn(&SubId<CID, SID>) -> bool,
268 {
269 (mounted_fn(id), mounted_fn)
270 }
271}