1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#![allow(clippy::unnecessary_cast)]

use bitflags::bitflags;
use std::ffi::CString;
use std::ptr::NonNull;
use typed_builder::TypedBuilder;

use super::{Window, WindowContextKind, WindowFormat};
use crate::{bind, Sdl, Video};

/// A coordinate value for the window position.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WindowCoord {
    coord: i32,
}

impl WindowCoord {
    /// Constructs from coord value. Must be in `-16384..=16384`.
    ///
    /// # Panics
    ///
    /// Panics if `coord` is not in `-16384..=16384`.
    #[must_use]
    pub fn coord(coord: i32) -> Self {
        const MAX: i32 = 16384;
        assert!((-MAX..=MAX).contains(&coord));
        Self { coord }
    }
    /// Constructs the undefined coordinate.
    #[must_use]
    pub const fn undefined() -> Self {
        Self {
            coord: 0x1FFF0000, // SDL_WINDOWPOS_UNDEFINED
        }
    }
    /// Constructs the centered coordinate.
    #[must_use]
    pub const fn centered() -> Self {
        Self {
            coord: 0x2FFF0000, // SDL_WINDOWPOS_CENTERED
        }
    }
    pub(super) fn into_arg(self) -> std::os::raw::c_int {
        self.coord
    }
}

/// A builder for the [`Window`].
#[derive(Debug, TypedBuilder)]
pub struct WindowBuilder {
    #[builder(default = "Untitled".into(), setter(into))]
    title: String,
    #[builder(default = WindowCoord::centered())]
    x: WindowCoord,
    #[builder(default = WindowCoord::centered())]
    y: WindowCoord,
    #[builder(default = 640)]
    width: u32,
    #[builder(default = 480)]
    height: u32,
    #[builder(default = WindowFormat::Normal)]
    format: WindowFormat,
    #[builder(default = WindowContextKind::Software)]
    context_kind: WindowContextKind,
    #[builder(default)]
    hidden: bool,
    #[builder(default)]
    allow_high_dpi: bool,
    #[builder(default)]
    borderless: bool,
    #[builder(default)]
    resizable: bool,
}

impl WindowBuilder {
    /// Builds the window.
    #[must_use]
    pub fn new_window<'video>(self, video: &'video Video) -> Window<'video> {
        if self.context_kind == WindowContextKind::Vulkan {
            let ret = unsafe { bind::SDL_Vulkan_LoadLibrary(std::ptr::null()) };
            if ret == -1 {
                Sdl::error_then_panic("loading vulkan library from SDL_VULKAN_LIBRARY");
            }
        }

        let flags = self.calc_flags();

        use std::os::raw::c_int;
        let cstr = CString::new(self.title).unwrap_or_default();
        let raw = unsafe {
            bind::SDL_CreateWindow(
                cstr.as_ptr(),
                self.x.into_arg(),
                self.y.into_arg(),
                self.width as c_int,
                self.height as c_int,
                flags,
            )
        };
        NonNull::new(raw).map_or_else(
            || Sdl::error_then_panic("Sdl window"),
            move |window| Window { window, video },
        )
    }

    fn calc_flags(&self) -> u32 {
        let mut flags = WindowFlags::empty();
        flags |= match self.format {
            WindowFormat::Normal => WindowFlags::empty(),
            WindowFormat::Maximized => WindowFlags::MAXIMIZED,
            WindowFormat::Minimized => WindowFlags::MINIMIZED,
            WindowFormat::FullScreen => WindowFlags::FULLSCREEN,
            WindowFormat::FullScreenWithCurrentDesktop => WindowFlags::FULLSCREEN_DESKTOP,
        };
        flags |= match self.context_kind {
            WindowContextKind::Software => WindowFlags::empty(),
            WindowContextKind::OpenGl => WindowFlags::OPENGL,
            WindowContextKind::Vulkan => WindowFlags::VULKAN,
            WindowContextKind::Metal => WindowFlags::METAL,
        };
        if self.hidden {
            flags |= WindowFlags::HIDDEN;
        }
        if self.allow_high_dpi {
            flags |= WindowFlags::ALLOW_HIGHDPI;
        }
        if self.borderless {
            flags |= WindowFlags::BORDERLESS;
        }
        if self.resizable {
            flags |= WindowFlags::RESIZABLE;
        }
        flags.bits()
    }
}

bitflags! {
    /// A flag for [`Window`].
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub(super) struct WindowFlags: u32 {
        const FULLSCREEN = bind::SDL_WINDOW_FULLSCREEN as u32;
        const FULLSCREEN_DESKTOP = bind::SDL_WINDOW_FULLSCREEN_DESKTOP as u32;
        const OPENGL = bind::SDL_WINDOW_OPENGL as u32;
        const VULKAN = bind::SDL_WINDOW_VULKAN as u32;
        const METAL = bind::SDL_WINDOW_METAL as u32;
        const SHOWN = bind::SDL_WINDOW_SHOWN as u32;
        const HIDDEN = bind::SDL_WINDOW_HIDDEN as u32;
        const BORDERLESS = bind::SDL_WINDOW_BORDERLESS as u32;
        const RESIZABLE = bind::SDL_WINDOW_RESIZABLE as u32;
        const MINIMIZED = bind::SDL_WINDOW_MINIMIZED as u32;
        const MAXIMIZED = bind::SDL_WINDOW_MAXIMIZED as u32;
        const INPUT_GRABBED = bind::SDL_WINDOW_INPUT_GRABBED as u32;
        const INPUT_FOCUS = bind::SDL_WINDOW_INPUT_FOCUS as u32;
        const MOUSE_FOCUS = bind::SDL_WINDOW_MOUSE_FOCUS as u32;
        const FOREIGN = bind::SDL_WINDOW_FOREIGN as u32;
        const ALLOW_HIGHDPI = bind::SDL_WINDOW_ALLOW_HIGHDPI as u32;
        const MOUSE_CAPTURE = bind::SDL_WINDOW_MOUSE_CAPTURE as u32;
    }
}