pyxel/
pyxel.rs

1use std::cmp::max;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::LazyLock;
4
5use crate::audio::Audio;
6use crate::channel::{Channel, SharedChannel};
7use crate::graphics::Graphics;
8use crate::image::{Image, Rgb24, SharedImage};
9use crate::input::Input;
10use crate::keys::Key;
11use crate::music::{Music, SharedMusic};
12use crate::resource::Resource;
13use crate::settings::{
14    CURSOR_DATA, CURSOR_HEIGHT, CURSOR_WIDTH, DEFAULT_COLORS, DEFAULT_FPS, DEFAULT_QUIT_KEY,
15    DEFAULT_TITLE, DEFAULT_TONES, DISPLAY_RATIO, FONT_DATA, FONT_HEIGHT, FONT_WIDTH, ICON_COLKEY,
16    ICON_DATA, ICON_SCALE, IMAGE_SIZE, NUM_CHANNELS, NUM_FONT_ROWS, NUM_IMAGES, NUM_MUSICS,
17    NUM_SAMPLES, NUM_SOUNDS, NUM_TILEMAPS, NUM_TONES, SAMPLE_RATE, TILEMAP_SIZE,
18};
19use crate::sound::{SharedSound, Sound};
20use crate::system::System;
21use crate::tilemap::{ImageSource, SharedTilemap, Tilemap};
22use crate::tone::{SharedTone, Tone};
23
24static IS_INITIALIZED: AtomicBool = AtomicBool::new(false);
25
26pub static COLORS: LazyLock<shared_type!(Vec<Rgb24>)> =
27    LazyLock::new(|| new_shared_type!(DEFAULT_COLORS.to_vec()));
28
29pub static IMAGES: LazyLock<shared_type!(Vec<SharedImage>)> = LazyLock::new(|| {
30    new_shared_type!((0..NUM_IMAGES)
31        .map(|_| Image::new(IMAGE_SIZE, IMAGE_SIZE))
32        .collect())
33});
34
35static TILEMAPS: LazyLock<shared_type!(Vec<SharedTilemap>)> = LazyLock::new(|| {
36    new_shared_type!((0..NUM_TILEMAPS)
37        .map(|_| Tilemap::new(TILEMAP_SIZE, TILEMAP_SIZE, ImageSource::Index(0)))
38        .collect())
39});
40
41static CURSOR_IMAGE: LazyLock<SharedImage> = LazyLock::new(|| {
42    let image = Image::new(CURSOR_WIDTH, CURSOR_HEIGHT);
43    image.lock().set(0, 0, &CURSOR_DATA);
44    image
45});
46
47pub static FONT_IMAGE: LazyLock<SharedImage> = LazyLock::new(|| {
48    let width = FONT_WIDTH * NUM_FONT_ROWS;
49    let height = FONT_HEIGHT * (FONT_DATA.len() as u32).div_ceil(NUM_FONT_ROWS);
50    let image = Image::new(width, height);
51    {
52        let mut image = image.lock();
53        for (fi, data) in FONT_DATA.iter().enumerate() {
54            let row = fi as u32 / NUM_FONT_ROWS;
55            let col = fi as u32 % NUM_FONT_ROWS;
56            let mut data = *data;
57            for yi in 0..FONT_HEIGHT {
58                for xi in 0..FONT_WIDTH {
59                    let x = FONT_WIDTH * col + xi;
60                    let y = FONT_HEIGHT * row + yi;
61                    let color = u8::from((data & 0x800000) != 0);
62                    image.canvas.write_data(x as usize, y as usize, color);
63                    data <<= 1;
64                }
65            }
66        }
67    }
68    image
69});
70
71pub static CHANNELS: LazyLock<shared_type!(Vec<SharedChannel>)> =
72    LazyLock::new(|| new_shared_type!((0..NUM_CHANNELS).map(|_| Channel::new()).collect()));
73
74pub static TONES: LazyLock<shared_type!(Vec<SharedTone>)> = LazyLock::new(|| {
75    new_shared_type!((0..NUM_TONES)
76        .map(|index| {
77            let tone = Tone::new();
78            {
79                let mut tone = tone.lock();
80                tone.gain = DEFAULT_TONES[index as usize].0;
81                tone.noise = DEFAULT_TONES[index as usize].1;
82                tone.waveform = DEFAULT_TONES[index as usize].2;
83            }
84            tone
85        })
86        .collect())
87});
88
89pub static SOUNDS: LazyLock<shared_type!(Vec<SharedSound>)> =
90    LazyLock::new(|| new_shared_type!((0..NUM_SOUNDS).map(|_| Sound::new()).collect()));
91
92static MUSICS: LazyLock<shared_type!(Vec<SharedMusic>)> =
93    LazyLock::new(|| new_shared_type!((0..NUM_MUSICS).map(|_| Music::new()).collect()));
94
95pub struct Pyxel {
96    // System
97    pub(crate) system: System,
98    pub width: u32,
99    pub height: u32,
100    pub frame_count: u32,
101
102    // Resource
103    pub(crate) resource: Resource,
104
105    // Input
106    pub(crate) input: Input,
107    pub mouse_x: i32,
108    pub mouse_y: i32,
109    pub mouse_wheel: i32,
110    pub input_keys: Vec<Key>,
111    pub input_text: String,
112    pub dropped_files: Vec<String>,
113
114    // Graphics
115    pub(crate) graphics: Graphics,
116    pub colors: shared_type!(Vec<Rgb24>),
117    pub images: shared_type!(Vec<SharedImage>),
118    pub tilemaps: shared_type!(Vec<SharedTilemap>),
119    pub screen: SharedImage,
120    pub cursor: SharedImage,
121    pub font: SharedImage,
122
123    // Audio
124    pub channels: shared_type!(Vec<SharedChannel>),
125    pub tones: shared_type!(Vec<SharedTone>),
126    pub sounds: shared_type!(Vec<SharedSound>),
127    pub musics: shared_type!(Vec<SharedMusic>),
128}
129
130pub fn init(
131    width: u32,
132    height: u32,
133    title: Option<&str>,
134    fps: Option<u32>,
135    quit_key: Option<Key>,
136    display_scale: Option<u32>,
137    capture_scale: Option<u32>,
138    capture_sec: Option<u32>,
139) -> Pyxel {
140    assert!(
141        !IS_INITIALIZED.swap(true, Ordering::Relaxed),
142        "Pyxel already initialized"
143    );
144
145    // Default parameters
146    let title = title.unwrap_or(DEFAULT_TITLE);
147    let quit_key = quit_key.unwrap_or(DEFAULT_QUIT_KEY);
148    let fps = fps.unwrap_or(DEFAULT_FPS);
149
150    // Platform
151    pyxel_platform::init(|display_width, display_height| {
152        let display_scale = max(
153            display_scale.map_or_else(
154                || {
155                    (f64::min(
156                        display_width as f64 / width as f64,
157                        display_height as f64 / height as f64,
158                    ) * DISPLAY_RATIO) as u32
159                },
160                |display_scale| display_scale,
161            ),
162            1,
163        );
164        (title, width * display_scale, height * display_scale)
165    });
166
167    // System
168    let system = System::new(fps, quit_key);
169    let frame_count = 0;
170
171    // Resource
172    let resource = Resource::new(capture_scale, capture_sec, fps);
173
174    // Input
175    let input = Input::new();
176    let mouse_x = 0;
177    let mouse_y = 0;
178    let mouse_wheel = 0;
179    let input_keys = Vec::new();
180    let input_text = String::new();
181    let dropped_files = Vec::new();
182
183    // Graphics
184    let graphics = Graphics::new();
185    let colors = COLORS.clone();
186    let images = IMAGES.clone();
187    let tilemaps = TILEMAPS.clone();
188    let screen = Image::new(width, height);
189    let cursor = CURSOR_IMAGE.clone();
190    let font = FONT_IMAGE.clone();
191
192    // Audio
193    let _ = Audio::new(SAMPLE_RATE, NUM_SAMPLES);
194    let channels = CHANNELS.clone();
195    let tones = TONES.clone();
196    let sounds = SOUNDS.clone();
197    let musics = MUSICS.clone();
198
199    let pyxel = Pyxel {
200        // System
201        system,
202        width,
203        height,
204        frame_count,
205
206        // Resource
207        resource,
208
209        // Input
210        input,
211        mouse_x,
212        mouse_y,
213        mouse_wheel,
214        input_keys,
215        input_text,
216        dropped_files,
217
218        // Graphics
219        graphics,
220        colors,
221        images,
222        tilemaps,
223        screen,
224        cursor,
225        font,
226
227        // Audio
228        channels,
229        tones,
230        sounds,
231        musics,
232    };
233
234    pyxel.icon(&ICON_DATA, ICON_SCALE, ICON_COLKEY);
235    pyxel
236}