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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Copyright 2020-2020 Juan Villacorta
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::marker::PhantomData;

use crate::asset_id::{AppAssetId, IdU16};
use crate::core::CoreAudio;

/// Context passed to methods in `App`.
pub struct AppContext<A: AppAssetId> {
    /// Audio playback.
    pub audio: Audio<A>,
    dims: (f64, f64),
    cursor: (f64, f64),
    close_requested: bool,
    native_px: f64,
    is_fullscreen: bool,
    desires_fullscreen: bool,
    cookie: Vec<u8>,
    cookie_updated: bool,
}

impl<A: AppAssetId> AppContext<A> {
    pub(crate) fn new(audio: CoreAudio, dims: (f64, f64), native_px: f64) -> AppContext<A> {
        AppContext {
            audio: Audio {
                core: audio,
                phantom: PhantomData,
            },
            dims,
            cursor: (0., 0.),
            close_requested: false,
            native_px,
            is_fullscreen: false,
            desires_fullscreen: false,
            cookie: Vec::new(),
            cookie_updated: false,
        }
    }

    pub(crate) fn set_cursor(&mut self, cursor: (f64, f64)) {
        self.cursor = cursor;
        self.bound_cursor();
    }

    pub(crate) fn set_dims(&mut self, dims: (f64, f64), native_px: f64) {
        self.dims = dims;
        self.native_px = native_px;
        self.bound_cursor();
    }

    fn bound_cursor(&mut self) {
        self.cursor = (
            self.cursor.0.max(0.).min(self.dims.0),
            self.cursor.1.max(0.).min(self.dims.1),
        );
    }

    /// Returns the app (width, height), which are restricted by the min/max dimensions
    /// specified in `AppInfo`.
    pub fn dims(&self) -> (f64, f64) {
        self.dims
    }

    /// Returns the mouse cursor (x, y) position in app coordinates.
    ///
    /// The x coordinate lies in the range `0` to `self.dims().0`.
    /// The y coordinate lies in the range `0` to `self.dims().1`.
    pub fn cursor(&self) -> (f64, f64) {
        self.cursor
    }

    /// Returns the width of a native pixel, measured in "app pixels".
    ///
    /// This value will always be at most 1.
    pub fn native_px(&self) -> f64 {
        self.native_px
    }

    /// Convenience method for aligning an `(x, y)` position to the nearest native pixel boundaries.
    ///
    /// This is typically used to align a camera position.
    /// See also `self.native_px()`.
    pub fn native_px_align(&self, x: f64, y: f64) -> (f64, f64) {
        (
            (x / self.native_px).round() * self.native_px,
            (y / self.native_px).round() * self.native_px,
        )
    }

    /// Requests the app to enter fullscreen mode.
    ///
    /// Depending on the target and how this method is invoked, the app may or may not
    /// actually enter fullscreen mode. When compiling to `wasm32-unknown-unknown` and
    /// running in a web browser, fullscreen requests can only be made successfully during
    /// certain user input events, so invoking fullscreen during `App.start` or `App.advance`
    /// will likely fail.
    pub fn request_fullscreen(&mut self) {
        self.desires_fullscreen = true;
    }

    /// Requests the app to cancel fullscreen mode.
    pub fn cancel_fullscreen(&mut self) {
        self.desires_fullscreen = false;
    }

    /// Checks whether or not the app is currently in fullscreen mode.
    ///
    /// This value will not change immediately after a call to `request_fullscreen` or
    /// `cancel_fullscreen`.
    pub fn is_fullscreen(&self) -> bool {
        self.is_fullscreen
    }

    pub(crate) fn desires_fullscreen(&self) -> bool {
        self.desires_fullscreen
    }

    pub(crate) fn set_is_fullscreen(&mut self, is_fullscreen: bool) {
        self.is_fullscreen = is_fullscreen;
        self.desires_fullscreen = is_fullscreen;
    }

    /// Closes the app entirely.
    ///
    /// When compiling to `wasm32-unknown-unknown`, the app may be resumed after it is closed
    /// via invoking a JavaScript method.
    pub fn close(&mut self) {
        self.close_requested = true;
    }

    pub(crate) fn take_close_request(&mut self) -> bool {
        let result = self.close_requested;
        self.close_requested = false;
        result
    }

    /// Gets current cookie data.
    ///
    /// NOTE: this API is likely to change change.
    /// Returns an empty array if cookie is not set or if not running in WebAssembly mode.
    pub fn cookie(&self) -> &[u8] {
        &self.cookie
    }

    /// Writes cookie data.
    ///
    /// NOTE: this API is likely to change change.
    /// Cookie can be used as lightweight save data when built in WebAssembly mode.
    /// Only writes persistent cookie data if built in WebAssembly mode.
    /// To use cookies, the readCookie and writeCookie functions must be passed into nuuro.js.
    pub fn set_cookie(&mut self, cookie: Vec<u8>) {
        assert!(cookie.len() < 700);
        if cookie != self.cookie {
            self.cookie_updated = true;
            self.cookie = cookie;
        }
    }

    #[cfg(target_arch = "wasm32")]
    pub(crate) fn take_cookie_updated_flag(&mut self) -> bool {
        let was_updated = self.cookie_updated;
        self.cookie_updated = false;
        was_updated
    }

    #[cfg(target_arch = "wasm32")]
    pub(crate) fn cookie_buffer(&mut self) -> &mut Vec<u8> {
        &mut self.cookie
    }
}

/// Struct for audio playback.
pub struct Audio<A: AppAssetId> {
    core: CoreAudio,
    phantom: PhantomData<A>,
}

impl<A: AppAssetId> Audio<A> {
    /// Plays the given sound effect once.
    pub fn play_sound(&mut self, sound: A::Sound) {
        self.core.play_sound(sound.id_u16());
    }

    /// Plays the given music once, replacing the currently playing music, if any.
    pub fn play_music(&mut self, music: A::Music) {
        self.core.play_music(music.id_u16(), false);
    }

    /// Continually loops the given music, replacing the currently playing music, if any.
    pub fn loop_music(&mut self, music: A::Music) {
        self.core.play_music(music.id_u16(), true);
    }

    /// Stops the currently playing music, if any.
    pub fn stop_music(&mut self) {
        self.core.stop_music();
    }
}