1use alloc::collections::BTreeMap;
8
9use azul_core::{
10 callbacks::LayoutCallback,
11 dom::DomId,
12 window::{
13 DebugState, ImePosition, KeyboardState, Monitor, MouseState, PlatformSpecificOptions,
14 RendererOptions, TouchState, WindowFlags, WindowPosition, WindowSize, WindowTheme,
15 },
16};
17use azul_css::{
18 corety::OptionU32, impl_option, impl_option_inner, impl_vec, impl_vec_clone, impl_vec_debug,
19 impl_vec_mut, impl_vec_partialeq, props::basic::OptionColorU, AzString,
20};
21
22use crate::callbacks::OptionCallback;
23
24#[derive(Debug, Clone, PartialEq)]
26#[repr(C)]
27pub struct WindowCreateOptions {
28 pub window_state: FullWindowState,
30 pub create_callback: OptionCallback,
32 pub renderer: azul_core::window::OptionRendererOptions,
34 pub theme: azul_core::window::OptionWindowTheme,
36 pub size_to_content: bool,
38 pub hot_reload: bool,
40 pub parent_window_id: u64,
47}
48
49impl Default for WindowCreateOptions {
50 fn default() -> Self {
51 Self {
52 window_state: FullWindowState::default(),
53 create_callback: OptionCallback::None,
54 renderer: azul_core::window::OptionRendererOptions::None,
55 theme: azul_core::window::OptionWindowTheme::None,
56 size_to_content: false,
57 hot_reload: false,
58 parent_window_id: 0,
59 }
60 }
61}
62
63impl WindowCreateOptions {
64 pub fn create(layout_callback: impl Into<LayoutCallback>) -> Self {
66 let mut options = Self::default();
67 options.window_state.layout_callback = layout_callback.into();
68 options
69 }
70}
71
72impl_option!(WindowCreateOptions, OptionWindowCreateOptions, copy = false, [Debug, Clone, PartialEq]);
73impl_vec!(WindowCreateOptions, WindowCreateOptionsVec, WindowCreateOptionsVecDestructor, WindowCreateOptionsVecDestructorType, WindowCreateOptionsVecSlice, OptionWindowCreateOptions);
74impl_vec_clone!(
75 WindowCreateOptions,
76 WindowCreateOptionsVec,
77 WindowCreateOptionsVecDestructor
78);
79impl_vec_partialeq!(WindowCreateOptions, WindowCreateOptionsVec);
80impl_vec_debug!(WindowCreateOptions, WindowCreateOptionsVec);
81impl_vec_mut!(WindowCreateOptions, WindowCreateOptionsVec);
82
83#[derive(Debug, Clone, PartialEq)]
85#[repr(C)]
86pub struct FullWindowState {
87 pub platform_specific_options: PlatformSpecificOptions,
89 pub keyboard_state: KeyboardState,
91 pub window_id: AzString,
94 pub title: AzString,
96 pub close_callback: OptionCallback,
98 pub layout_callback: LayoutCallback,
100 pub position: WindowPosition,
102 pub touch_state: TouchState,
104 pub size: WindowSize,
106 pub flags: WindowFlags,
108 pub mouse_state: MouseState,
110 pub theme: WindowTheme,
112 pub ime_position: ImePosition,
114 pub renderer_options: RendererOptions,
116 pub monitor_id: OptionU32,
118 pub debug_state: DebugState,
120 pub background_color: OptionColorU,
122 pub window_focused: bool,
124 pub active_route: azul_core::resources::OptionRouteMatch,
128}
129
130impl_option!(
131 FullWindowState,
132 OptionFullWindowState,
133 copy = false,
134 [Debug, Clone, PartialEq]
135);
136
137impl_vec!(
140 FullWindowState,
141 FullWindowStateVec,
142 FullWindowStateVecDestructor,
143 FullWindowStateVecDestructorType,
144 FullWindowStateVecSlice,
145 OptionFullWindowState
146);
147impl_vec_clone!(
148 FullWindowState,
149 FullWindowStateVec,
150 FullWindowStateVecDestructor
151);
152impl_vec_partialeq!(FullWindowState, FullWindowStateVec);
153impl_vec_debug!(FullWindowState, FullWindowStateVec);
154
155impl Default for FullWindowState {
156 fn default() -> Self {
157 Self {
158 platform_specific_options: PlatformSpecificOptions::default(),
159 keyboard_state: KeyboardState::default(),
160 window_id: AzString::from_const_str("azul-window"),
161 title: AzString::from_const_str("Azul Window"),
162 close_callback: OptionCallback::None,
163 layout_callback: LayoutCallback::default(),
164 position: WindowPosition::default(),
165 touch_state: TouchState::default(),
166 size: WindowSize::default(),
167 flags: WindowFlags::default(),
168 mouse_state: MouseState::default(),
169 theme: WindowTheme::default(),
170 ime_position: ImePosition::default(),
171 renderer_options: RendererOptions::default(),
172 monitor_id: OptionU32::None,
173 debug_state: DebugState::default(),
174 background_color: OptionColorU::None,
175 window_focused: true,
176 active_route: azul_core::resources::OptionRouteMatch::None,
177 }
178 }
179}
180
181#[cfg(test)]
182mod autotest_generated {
183 use azul_core::{
184 callbacks::{LayoutCallbackInfo, LayoutCallbackType, Update},
185 dom::Dom,
186 geom::{LogicalSize, PhysicalPositionI32},
187 refany::RefAny,
188 resources::{OptionRouteMatch, RouteMatch},
189 window::{AzStringPair, OptionWindowTheme, StringPairVec},
190 };
191 use azul_css::props::basic::ColorU;
192
193 use super::*;
194 use crate::callbacks::{Callback, CallbackInfo};
195
196 extern "C" fn cb_alpha(_: RefAny, _: LayoutCallbackInfo) -> Dom {
208 Dom::create_body()
209 }
210
211 extern "C" fn cb_beta(_: RefAny, _: LayoutCallbackInfo) -> Dom {
212 Dom::create_text("beta")
213 }
214
215 extern "C" fn cb_gamma(_: RefAny, _: LayoutCallbackInfo) -> Dom {
216 Dom::create_text("gamma-gamma-gamma")
217 }
218
219 extern "C" fn cb_never_called(_: RefAny, _: LayoutCallbackInfo) -> Dom {
221 unreachable!("WindowCreateOptions::create must not invoke the layout callback")
222 }
223
224 extern "C" fn close_cb(_: RefAny, _: CallbackInfo) -> Update {
225 Update::DoNothing
226 }
227
228 fn ptr_of(f: LayoutCallbackType) -> usize {
229 f as usize
230 }
231
232 fn stored_cb(o: &WindowCreateOptions) -> usize {
233 o.window_state.layout_callback.cb as usize
234 }
235
236 fn state_with(mutate: impl FnOnce(&mut FullWindowState)) -> FullWindowState {
239 let mut s = FullWindowState::default();
240 mutate(&mut s);
241 s
242 }
243
244 fn options_with(mutate: impl FnOnce(&mut WindowCreateOptions)) -> WindowCreateOptions {
245 let mut o = WindowCreateOptions::default();
246 mutate(&mut o);
247 o
248 }
249
250 fn opts_with_id(id: u64) -> WindowCreateOptions {
251 let mut o = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
252 o.parent_window_id = id;
253 o
254 }
255
256 fn ids_of(v: &WindowCreateOptionsVec) -> Vec<u64> {
257 v.as_slice().iter().map(|o| o.parent_window_id).collect()
258 }
259
260 fn nasty_strings() -> Vec<String> {
264 vec![
265 String::new(),
266 "\0".to_string(),
267 "a\0b\0".to_string(),
268 "๐ฆ๐๐จโ๐ฉโ๐งโ๐ฆ".to_string(),
269 "ู
ุฑุญุจุง ุจุงูุนุงูู
".to_string(),
270 "e\u{0301}\u{0301}\u{0301}".to_string(),
271 "\u{200B}\u{FEFF}\u{202E}drowssap".to_string(),
272 "๏ฝ๏ฝ๏ฝ๏ฝ๏ฝ๏ฝ๏ฝ๏ฝ๏ฝ".to_string(),
273 "ร".repeat(65_536),
274 ]
275 }
276
277 #[test]
282 fn create_stores_the_callback_and_leaves_every_other_field_at_default() {
283 let opts = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
284 let def = WindowCreateOptions::default();
285
286 assert_eq!(stored_cb(&opts), ptr_of(cb_alpha));
287 assert!(opts.window_state.layout_callback.ctx.is_none());
288
289 assert_eq!(opts.create_callback, def.create_callback);
290 assert_eq!(opts.renderer, def.renderer);
291 assert_eq!(opts.theme, def.theme);
292 assert_eq!(opts.size_to_content, def.size_to_content);
293 assert_eq!(opts.hot_reload, def.hot_reload);
294 assert_eq!(opts.parent_window_id, def.parent_window_id);
295 assert_eq!(opts.window_state.title, def.window_state.title);
296 assert_eq!(opts.window_state.window_id, def.window_state.window_id);
297 assert_eq!(
298 opts.window_state.close_callback,
299 def.window_state.close_callback
300 );
301 assert_eq!(
302 opts.window_state.window_focused,
303 def.window_state.window_focused
304 );
305 }
306
307 #[test]
308 fn create_records_exactly_the_pointer_it_was_given() {
309 let all: [LayoutCallbackType; 4] = [cb_alpha, cb_beta, cb_gamma, cb_never_called];
310 for cb in all.iter().copied() {
311 let opts = WindowCreateOptions::create(cb);
312 assert_eq!(stored_cb(&opts), cb as usize);
313 }
314 }
315
316 #[test]
317 fn create_does_not_invoke_the_stored_callback() {
318 let opts = WindowCreateOptions::create(cb_never_called as LayoutCallbackType);
321 assert_eq!(stored_cb(&opts), ptr_of(cb_never_called));
322 }
323
324 #[test]
325 fn create_with_the_default_callback_equals_default_options() {
326 let opts = WindowCreateOptions::create(LayoutCallback::default());
327 assert_eq!(opts, WindowCreateOptions::default());
328 }
329
330 #[test]
331 fn create_accepts_a_layout_callback_value_and_preserves_its_ctx() {
332 let cb = LayoutCallback {
335 cb: cb_beta,
336 ctx: Some(RefAny::new(vec![0xAAu8; 4096])).into(),
337 };
338 let opts = WindowCreateOptions::create(cb);
339 assert_eq!(stored_cb(&opts), ptr_of(cb_beta));
340 assert!(opts.window_state.layout_callback.ctx.is_some());
341
342 let opts =
344 WindowCreateOptions::create(LayoutCallback::create(cb_gamma as LayoutCallbackType));
345 assert_eq!(stored_cb(&opts), ptr_of(cb_gamma));
346 assert!(opts.window_state.layout_callback.ctx.is_none());
347 }
348
349 #[test]
350 fn create_is_deterministic_and_independent_across_calls() {
351 let first = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
352 for _ in 0..500 {
353 let next = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
354 assert_eq!(next, first);
355 }
356
357 let mut a = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
359 a.parent_window_id = u64::MAX;
360 a.window_state.title = AzString::from("mutated");
361 let b = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
362 assert_eq!(b, first);
363 assert_ne!(a, b);
364 }
365
366 #[test]
371 fn full_window_state_default_invariants() {
372 let s = FullWindowState::default();
373
374 assert_eq!(s.window_id.as_str(), "azul-window");
375 assert_eq!(s.title.as_str(), "Azul Window");
376 assert!(s.window_focused);
377 assert!(s.monitor_id.is_none());
378 assert!(s.background_color.is_none());
379 assert!(s.active_route.is_none());
380 assert_eq!(s.close_callback, OptionCallback::None);
381 assert_eq!(s.theme, WindowTheme::default());
382 assert_eq!(s.position, WindowPosition::default());
383 assert_eq!(s.ime_position, ImePosition::default());
384
385 assert_eq!(s, FullWindowState::default());
387 assert_eq!(s.clone(), s);
388 }
389
390 #[test]
391 fn window_create_options_default_invariants() {
392 let o = WindowCreateOptions::default();
393
394 assert_eq!(o.parent_window_id, 0, "0 means 'no parent window'");
395 assert!(!o.size_to_content);
396 assert!(!o.hot_reload);
397 assert!(o.renderer.is_none());
398 assert!(o.theme.is_none());
399 assert_eq!(o.create_callback, OptionCallback::None);
400 assert_eq!(o.window_state, FullWindowState::default());
401
402 assert_eq!(o, WindowCreateOptions::default());
403 assert_eq!(o.clone(), o);
404 }
405
406 #[test]
411 fn mutating_any_scalar_field_breaks_equality() {
412 let base = WindowCreateOptions::default();
413
414 assert_ne!(options_with(|o| o.parent_window_id = u64::MAX), base);
415 assert_ne!(options_with(|o| o.parent_window_id = 1), base);
416 assert_ne!(options_with(|o| o.size_to_content = true), base);
417 assert_ne!(options_with(|o| o.hot_reload = true), base);
418 assert_ne!(
419 options_with(|o| o.theme = OptionWindowTheme::Some(WindowTheme::DarkMode)),
420 base
421 );
422 assert_ne!(
423 options_with(|o| o.create_callback =
424 OptionCallback::Some(Callback::from(close_cb as crate::callbacks::CallbackType))),
425 base
426 );
427 assert_ne!(
428 options_with(|o| o.window_state.window_focused = false),
429 base
430 );
431 assert_ne!(
432 options_with(|o| o.window_state.monitor_id = OptionU32::Some(u32::MAX)),
433 base
434 );
435 assert_ne!(
436 options_with(|o| o.window_state.monitor_id = OptionU32::Some(0)),
437 base
438 );
439 assert_ne!(
440 options_with(
441 |o| o.window_state.background_color = OptionColorU::Some(ColorU {
442 r: 0,
443 g: 0,
444 b: 0,
445 a: 0,
446 })
447 ),
448 base
449 );
450 assert_ne!(
451 options_with(|o| o.window_state.title = AzString::from("")),
452 base
453 );
454 }
455
456 #[test]
457 #[allow(clippy::eq_op)] fn equality_is_reflexive_symmetric_and_transitive_for_extreme_values() {
459 let a = options_with(|o| {
460 o.parent_window_id = u64::MAX;
461 o.window_state.title = AzString::from("๐ฆ\0๐ฆ");
462 o.window_state.size.dimensions = LogicalSize::new(f32::NAN, -0.0);
463 });
464 let b = a.clone();
465 let c = b.clone();
466
467 assert_eq!(a, a); assert_eq!(a, b);
469 assert_eq!(b, a); assert_eq!(b, c);
471 assert_eq!(a, c); assert_ne!(a, WindowCreateOptions::default());
473 }
474
475 #[test]
476 fn callback_ctx_is_deliberately_not_part_of_equality() {
477 let a = WindowCreateOptions::create(cb_alpha as LayoutCallbackType);
481 let mut b = a.clone();
482 b.window_state.layout_callback.ctx = Some(RefAny::new(0xDEAD_BEEF_u32)).into();
483
484 assert!(a.window_state.layout_callback.ctx.is_none());
485 assert!(b.window_state.layout_callback.ctx.is_some());
486 assert_eq!(a, b);
487 }
488
489 #[test]
494 fn nan_dimensions_stay_reflexive_and_do_not_alias_the_origin() {
495 let nan = state_with(|s| s.size.dimensions = LogicalSize::new(f32::NAN, f32::NAN));
496 let zero = state_with(|s| s.size.dimensions = LogicalSize::new(0.0, 0.0));
497
498 assert_eq!(nan, nan.clone());
501 assert_ne!(nan, zero);
502 assert_ne!(nan, FullWindowState::default());
503 }
504
505 #[test]
506 fn out_of_range_dimensions_saturate_rather_than_wrap() {
507 let inf = state_with(|s| {
511 s.size.dimensions = LogicalSize::new(f32::INFINITY, f32::NEG_INFINITY);
512 });
513 let max = state_with(|s| s.size.dimensions = LogicalSize::new(f32::MAX, -f32::MAX));
514
515 assert_eq!(inf, inf.clone());
516 assert_eq!(max, max.clone());
517 assert_eq!(
518 inf, max,
519 "f32::MAX * 1000 overflows to inf, so both saturate"
520 );
521
522 let tiny = state_with(|s| {
525 s.size.dimensions = LogicalSize::new(f32::MIN_POSITIVE, -f32::MIN_POSITIVE);
526 });
527 assert_eq!(
528 tiny,
529 state_with(|s| s.size.dimensions = LogicalSize::new(0.0, -0.0))
530 );
531 }
532
533 #[test]
534 fn integer_window_state_limits_survive_clone_and_containers() {
535 let extreme = state_with(|s| {
536 s.size.dpi = 0; s.size.min_dimensions = Some(LogicalSize::new(-0.0, 0.0)).into();
538 s.size.max_dimensions = Some(LogicalSize::new(f32::INFINITY, f32::INFINITY)).into();
539 s.monitor_id = OptionU32::Some(u32::MAX);
540 s.position = WindowPosition::Initialized(PhysicalPositionI32 {
541 x: i32::MIN,
542 y: i32::MAX,
543 });
544 s.background_color = OptionColorU::Some(ColorU {
545 r: u8::MAX,
546 g: 0,
547 b: u8::MAX,
548 a: 0,
549 });
550 });
551
552 let cloned = extreme.clone();
553 assert_eq!(cloned, extreme);
554 assert_eq!(cloned.size.dpi, 0);
555 assert_eq!(cloned.monitor_id.into_option(), Some(u32::MAX));
556 assert_eq!(
557 cloned.position,
558 WindowPosition::Initialized(PhysicalPositionI32 {
559 x: i32::MIN,
560 y: i32::MAX,
561 })
562 );
563
564 let round_tripped: Option<FullWindowState> = OptionFullWindowState::Some(cloned).into();
565 assert_eq!(round_tripped, Some(extreme.clone()));
566
567 let v = FullWindowStateVec::from_vec(vec![extreme.clone()]);
568 assert_eq!(v.as_slice()[0], extreme);
569 }
570
571 #[test]
572 fn parent_window_id_boundaries_survive_every_round_trip() {
573 let ids: [u64; 5] = [0, 1, u64::MAX / 2, u64::MAX - 1, u64::MAX];
574 for id in ids.iter().copied() {
575 let o = opts_with_id(id);
576 assert_eq!(o.parent_window_id, id);
577 assert_eq!(o.clone().parent_window_id, id);
578
579 let opt: OptionWindowCreateOptions = Some(o.clone()).into();
580 assert_eq!(opt.as_ref().map(|x| x.parent_window_id), Some(id));
581
582 let v = WindowCreateOptionsVec::from_vec(vec![o.clone()]);
583 assert_eq!(v.as_slice()[0].parent_window_id, id);
584
585 let out: Vec<WindowCreateOptions> = v.into();
586 assert_eq!(out[0], o);
587 }
588 }
589
590 #[test]
595 fn unicode_and_huge_strings_round_trip_through_state_and_containers() {
596 for s in nasty_strings() {
597 let st = state_with(|w| {
598 w.title = AzString::from(s.clone());
599 w.window_id = AzString::from(s.clone());
600 });
601
602 assert_eq!(st.title.as_str(), s.as_str());
603 assert_eq!(st.title.as_str().len(), s.len(), "byte length preserved");
604 assert_eq!(st.window_id.as_str(), s.as_str());
605
606 let cloned = st.clone();
607 assert_eq!(cloned, st);
608 assert_eq!(cloned.title.as_str(), s.as_str());
609
610 let opt: OptionFullWindowState = Some(st.clone()).into();
612 let back: Option<FullWindowState> = opt.into();
613 assert_eq!(back.as_ref().map(|x| x.title.as_str()), Some(s.as_str()));
614
615 let v = FullWindowStateVec::from_vec(vec![st.clone(), st.clone()]);
617 assert_eq!(v.len(), 2);
618 assert_eq!(v.as_slice()[1].title.as_str(), s.as_str());
619 assert_eq!(v.clone(), v);
620
621 let out: Vec<FullWindowState> = v.into_library_owned_vec();
622 assert_eq!(out.len(), 2);
623 assert_eq!(out[0].title.as_str(), s.as_str());
624 assert_eq!(out[0], st);
625 }
626 }
627
628 #[test]
629 fn debug_rendering_survives_nasty_strings() {
630 for s in nasty_strings().into_iter().filter(|s| s.len() < 4096) {
631 let o = options_with(|w| {
632 w.window_state.title = AzString::from(s.clone());
633 w.parent_window_id = u64::MAX;
634 });
635 let rendered = format!("{o:?}");
636 assert!(rendered.starts_with("WindowCreateOptions"));
637 assert!(rendered.contains("18446744073709551615"));
638 }
639 }
640
641 #[test]
642 fn active_route_round_trips_with_unicode_pattern_and_params() {
643 let route = RouteMatch {
644 pattern: AzString::from("/user/:id/๐ฆ"),
645 params: StringPairVec::from_vec(vec![AzStringPair {
646 key: AzString::from("id"),
647 value: AzString::from("๐๐"),
648 }]),
649 };
650 let st = state_with(|s| s.active_route = OptionRouteMatch::Some(route.clone()));
651
652 assert!(st.active_route.is_some());
653 assert_ne!(st, FullWindowState::default());
654
655 let opt: OptionFullWindowState = Some(st.clone()).into();
656 let back = opt.into_option().expect("just constructed as Some");
657 assert_eq!(back, st);
658 assert_eq!(
659 back.active_route.as_ref().map(|r| r.pattern.as_str()),
660 Some("/user/:id/๐ฆ")
661 );
662 assert_eq!(
663 back.active_route
664 .as_ref()
665 .and_then(|r| r.get_param("id"))
666 .map(|v| v.as_str()),
667 Some("๐๐")
668 );
669 assert!(back
670 .active_route
671 .as_ref()
672 .and_then(|r| r.get_param("missing"))
673 .is_none());
674 }
675
676 #[test]
681 fn option_wrappers_agree_with_std_option() {
682 let opts = WindowCreateOptions::create(cb_beta as LayoutCallbackType);
683
684 let none: OptionWindowCreateOptions = Option::<WindowCreateOptions>::None.into();
685 assert!(none.is_none());
686 assert!(!none.is_some());
687 assert!(none.as_ref().is_none());
688 assert!(none.as_option().is_none());
689 assert_eq!(none, OptionWindowCreateOptions::default());
690 let back: Option<WindowCreateOptions> = none.into();
691 assert_eq!(back, None);
692
693 let some: OptionWindowCreateOptions = Some(opts.clone()).into();
694 assert!(some.is_some());
695 assert!(!some.is_none());
696 assert_eq!(some.as_ref(), Some(&opts));
697 assert_eq!(some.into_option(), Some(opts.clone()));
698 assert_eq!(some.clone().map(|o| o.parent_window_id), Some(0));
699 assert_eq!(
700 some.clone()
701 .and_then(|o| o.window_state.title.as_str().chars().next()),
702 Some('A')
703 );
704 let back: Option<WindowCreateOptions> = some.into();
705 assert_eq!(back, Some(opts));
706 }
707
708 #[test]
709 fn option_replace_returns_the_previous_value() {
710 let a = opts_with_id(1);
711 let b = opts_with_id(2);
712
713 let mut o = OptionWindowCreateOptions::None;
714 assert!(o.replace(a.clone()).is_none());
715 assert_eq!(o.as_ref(), Some(&a));
716
717 let prev = o.replace(b.clone());
718 assert_eq!(prev.as_ref(), Some(&a));
719 assert_eq!(o.as_ref(), Some(&b));
720
721 if let Some(inner) = o.as_mut() {
722 inner.parent_window_id = u64::MAX;
723 }
724 assert_eq!(o.as_ref().map(|x| x.parent_window_id), Some(u64::MAX));
725 assert_eq!(prev.as_ref().map(|x| x.parent_window_id), Some(1));
727 }
728
729 #[test]
730 fn option_into_option_hands_back_an_independent_clone() {
731 let mut o: OptionFullWindowState = Some(FullWindowState::default()).into();
732
733 let mut taken = o.into_option().expect("constructed as Some");
734 taken.title = AzString::from("changed");
735 assert_eq!(
736 o.as_ref().map(|s| s.title.as_str()),
737 Some("Azul Window"),
738 "into_option must clone, not alias"
739 );
740
741 if let Some(inner) = o.as_mut() {
742 inner.title = AzString::from("also changed");
743 }
744 assert_eq!(taken.title.as_str(), "changed");
745 }
746
747 #[test]
752 fn vec_round_trips_empty_single_and_large() {
753 for n in [0usize, 1, 2, 1000].iter().copied() {
754 let src: Vec<WindowCreateOptions> = (0..n as u64).map(opts_with_id).collect();
755 let v = WindowCreateOptionsVec::from_vec(src.clone());
756
757 assert_eq!(v.len(), n);
758 assert_eq!(v.is_empty(), n == 0);
759 assert!(v.capacity() >= v.len());
760 assert_eq!(v.as_slice(), src.as_slice());
761 assert_eq!(v.iter().count(), n);
762 assert_eq!(v.as_c_slice().len(), n);
763 assert_eq!(v.as_c_slice().as_slice(), src.as_slice());
764
765 let out: Vec<WindowCreateOptions> = v.into_library_owned_vec();
766 assert_eq!(out, src, "encode == decode");
767 }
768 }
769
770 #[test]
771 fn vec_get_is_none_out_of_bounds() {
772 let v = WindowCreateOptionsVec::from_vec(vec![opts_with_id(7)]);
773
774 assert_eq!(v.get(0).map(|o| o.parent_window_id), Some(7));
775 assert!(v.get(1).is_none());
776 assert!(v.get(usize::MAX).is_none());
777 assert!(v.c_get(1).is_none());
778 assert!(v.c_get(usize::MAX).is_none());
779 assert_eq!(
780 v.c_get(0).into_option().map(|o| o.parent_window_id),
781 Some(7)
782 );
783
784 let empty = WindowCreateOptionsVec::new();
785 assert!(empty.is_empty());
786 assert!(empty.get(0).is_none());
787 assert!(empty.get(usize::MAX).is_none());
788 assert!(empty.c_get(0).is_none());
789 assert_eq!(empty.as_slice(), &[] as &[WindowCreateOptions]);
790 }
791
792 #[test]
793 fn vec_c_slice_range_clamps_instead_of_panicking() {
794 let v = WindowCreateOptionsVec::from_vec((0..4u64).map(opts_with_id).collect());
795
796 assert_eq!(v.as_c_slice_range(0, 4).len(), 4);
797 assert_eq!(v.as_c_slice_range(1, 3).len(), 2);
798 assert_eq!(v.as_c_slice_range(1, 3).as_slice()[0].parent_window_id, 1);
799 assert_eq!(v.as_c_slice_range(3, 1).len(), 0, "inverted range -> empty");
800 assert_eq!(v.as_c_slice_range(4, 4).len(), 0);
801 assert_eq!(
802 v.as_c_slice_range(0, usize::MAX).len(),
803 4,
804 "end clamps to len"
805 );
806 assert_eq!(v.as_c_slice_range(2, usize::MAX).as_slice().len(), 2);
807 assert_eq!(v.as_c_slice_range(usize::MAX, usize::MAX).len(), 0);
808 assert_eq!(v.as_c_slice_range(usize::MAX, 0).len(), 0);
809
810 let empty = WindowCreateOptionsVec::new();
811 assert!(empty.as_c_slice_range(0, usize::MAX).is_empty());
812 assert!(empty.as_c_slice_range(usize::MAX, usize::MAX).is_empty());
813 assert!(empty.as_c_slice().as_slice().is_empty());
814 }
815
816 #[test]
817 fn vec_insert_and_remove_are_noops_out_of_bounds() {
818 let mut v = WindowCreateOptionsVec::from_vec(vec![opts_with_id(1), opts_with_id(2)]);
819
820 v.insert(3, opts_with_id(99)); assert_eq!(ids_of(&v), vec![1, 2]);
822 v.insert(usize::MAX, opts_with_id(99));
823 assert_eq!(ids_of(&v), vec![1, 2]);
824
825 v.remove(2); assert_eq!(ids_of(&v), vec![1, 2]);
827 v.remove(usize::MAX);
828 assert_eq!(ids_of(&v), vec![1, 2]);
829
830 v.insert(0, opts_with_id(0)); v.insert(3, opts_with_id(3)); assert_eq!(ids_of(&v), vec![0, 1, 2, 3]);
833
834 v.remove(0);
835 assert_eq!(ids_of(&v), vec![1, 2, 3]);
836 v.remove(2);
837 assert_eq!(ids_of(&v), vec![1, 2]);
838 }
839
840 #[test]
841 fn vec_push_pop_truncate_retain_keep_len_and_capacity_consistent() {
842 let mut v = WindowCreateOptionsVec::new();
843 assert!(v.pop().is_none(), "pop on an empty vec must not panic");
844
845 for i in 0..256u64 {
846 v.push(opts_with_id(i));
847 assert_eq!(v.len(), i as usize + 1);
848 assert!(v.capacity() >= v.len());
849 }
850
851 assert_eq!(v.get(255).map(|o| o.parent_window_id), Some(255));
852 assert_eq!(v.pop().map(|o| o.parent_window_id), Some(255));
853 assert_eq!(v.len(), 255);
854
855 v.truncate(1000); assert_eq!(v.len(), 255);
857
858 v.retain(|o| o.parent_window_id % 2 == 0);
859 assert_eq!(v.len(), 128);
860 assert!(v.as_slice().iter().all(|o| o.parent_window_id % 2 == 0));
861
862 v.truncate(0);
863 assert!(v.is_empty());
864 assert!(v.pop().is_none());
865 assert_eq!(v.as_slice(), &[] as &[WindowCreateOptions]);
866 }
867
868 #[test]
869 fn vec_append_moves_everything_and_empties_the_source() {
870 let mut a = WindowCreateOptionsVec::from_vec(vec![opts_with_id(1)]);
871 let mut b = WindowCreateOptionsVec::from_vec(vec![opts_with_id(2), opts_with_id(3)]);
872
873 a.append(&mut b);
874 assert_eq!(ids_of(&a), vec![1, 2, 3]);
875 assert!(b.is_empty());
876
877 let mut empty = WindowCreateOptionsVec::new();
878 a.append(&mut empty); assert_eq!(ids_of(&a), vec![1, 2, 3]);
880
881 empty.append(&mut a); assert_eq!(ids_of(&empty), vec![1, 2, 3]);
883 assert!(a.is_empty());
884 }
885
886 #[test]
887 fn vec_clone_is_deep_and_growth_does_not_alias_the_original() {
888 let original = WindowCreateOptionsVec::from_vec(vec![opts_with_id(1), opts_with_id(2)]);
889 let mut cloned = original.clone();
890
891 assert_eq!(cloned, original);
892 assert_ne!(
893 cloned.as_ptr(),
894 original.as_ptr(),
895 "a clone must own a separate buffer"
896 );
897
898 for i in 0..64u64 {
899 cloned.push(opts_with_id(100 + i));
900 }
901 assert_eq!(cloned.len(), 66);
902 assert_eq!(ids_of(&original), vec![1, 2], "original must be untouched");
903
904 for o in cloned.iter_mut() {
905 o.hot_reload = true;
906 }
907 assert!(!original.as_slice()[0].hot_reload);
908 }
909
910 #[test]
911 fn vec_from_iterator_extend_and_sort_are_consistent() {
912 let mut v: WindowCreateOptionsVec = (0..8u64).rev().map(opts_with_id).collect();
913 assert_eq!(v.len(), 8);
914 assert_eq!(ids_of(&v), vec![7, 6, 5, 4, 3, 2, 1, 0]);
915
916 v.extend((8..12u64).map(opts_with_id));
917 assert_eq!(v.len(), 12);
918
919 v.sort_by(|a, b| a.parent_window_id.cmp(&b.parent_window_id));
920 assert_eq!(ids_of(&v), (0..12u64).collect::<Vec<_>>());
921
922 let back: Vec<WindowCreateOptions> = v.into();
923 assert_eq!(back.len(), 12);
924 let again: WindowCreateOptionsVec = back.into();
925 assert_eq!(ids_of(&again), (0..12u64).collect::<Vec<_>>());
926
927 let single = WindowCreateOptionsVec::from_item(opts_with_id(42));
928 assert_eq!(ids_of(&single), vec![42]);
929
930 let reserved = WindowCreateOptionsVec::with_capacity(0);
931 assert!(reserved.is_empty());
932 }
933
934 #[test]
935 fn vec_equality_and_debug_are_well_behaved() {
936 let a = WindowCreateOptionsVec::from_vec(vec![opts_with_id(1)]);
937 let b = WindowCreateOptionsVec::from_vec(vec![opts_with_id(1)]);
938 let c = WindowCreateOptionsVec::from_vec(vec![opts_with_id(2)]);
939 let longer = WindowCreateOptionsVec::from_vec(vec![opts_with_id(1), opts_with_id(1)]);
940 let empty = WindowCreateOptionsVec::new();
941
942 assert_eq!(a, b);
943 assert_ne!(a, c);
944 assert_ne!(a, longer, "length is part of equality");
945 assert_ne!(a, empty);
946 assert_eq!(empty, WindowCreateOptionsVec::default());
947 assert_eq!(format!("{empty:?}"), "[]");
948 assert!(format!("{a:?}").starts_with('['));
949 }
950
951 #[test]
956 fn full_window_state_vec_round_trips_and_clones_deeply() {
957 let extreme = state_with(|s| {
958 s.title = AzString::from("t".repeat(4096));
959 s.size.dimensions = LogicalSize::new(f32::NAN, f32::INFINITY);
960 s.monitor_id = OptionU32::Some(u32::MAX);
961 s.window_focused = false;
962 });
963
964 let v = FullWindowStateVec::from_vec(vec![FullWindowState::default(), extreme.clone()]);
965 assert_eq!(v.len(), 2);
966 assert!(!v.is_empty());
967 assert!(v.get(2).is_none());
968 assert!(v.get(usize::MAX).is_none());
969 assert!(v.c_get(2).is_none());
970 assert_eq!(v.c_get(1).into_option().as_ref(), Some(&extreme));
971 assert_eq!(v.iter().count(), 2);
972 assert_eq!(v.as_c_slice_range(1, usize::MAX).len(), 1);
973
974 let cloned = v.clone();
975 assert_eq!(cloned, v);
976 assert_ne!(cloned.as_ptr(), v.as_ptr());
977
978 let out: Vec<FullWindowState> = cloned.into_library_owned_vec();
979 assert_eq!(out.len(), 2);
980 assert_eq!(out[0], FullWindowState::default());
981 assert_eq!(out[1], extreme);
982 assert_eq!(v.len(), 2, "original survives the clone being consumed");
983
984 let single = FullWindowStateVec::from_item(extreme.clone());
985 assert_eq!(single.len(), 1);
986 assert_eq!(single.as_slice()[0], extreme);
987
988 let empty = FullWindowStateVec::new();
989 assert!(empty.is_empty());
990 assert!(empty.get(0).is_none());
991 assert_eq!(empty, FullWindowStateVec::default());
992 assert_eq!(format!("{empty:?}"), "[]");
993 }
994
995 #[test]
996 fn state_vec_survives_a_large_batch_of_extreme_states() {
997 let src: Vec<FullWindowState> = (0..512u32)
998 .map(|i| {
999 state_with(|s| {
1000 s.monitor_id = OptionU32::Some(i);
1001 s.title = AzString::from(format!("win-{i}-๐ฆ"));
1002 s.size.dimensions = LogicalSize::new(i as f32, f32::NAN);
1003 })
1004 })
1005 .collect();
1006
1007 let v = FullWindowStateVec::from_vec(src.clone());
1008 assert_eq!(v.len(), 512);
1009 assert_eq!(v.clone(), v);
1010 assert_eq!(v.as_slice()[511].title.as_str(), "win-511-๐ฆ");
1011
1012 let out: Vec<FullWindowState> = v.into_library_owned_vec();
1013 assert_eq!(out, src, "encode == decode for 512 extreme states");
1014 }
1015}