diff --git a/src/builtin/locale/en.json b/src/builtin/locale/en.json index af42887..ce901b3 100644 --- a/src/builtin/locale/en.json +++ b/src/builtin/locale/en.json @@ -61,6 +61,11 @@ "options_menu": { "graphics": "Graphics...", "graphics_menu": { + "window_mode": { + "entry": "Display mode:", + "windowed": "Windowed", + "fullscreen": "Fullscreen" + }, "lighting_effects": "Lighting effects:", "weapon_light_cone": "Weapon light cone:", "screen_shake": { diff --git a/src/builtin/locale/jp.json b/src/builtin/locale/jp.json index 34afe31..0f09bfd 100644 --- a/src/builtin/locale/jp.json +++ b/src/builtin/locale/jp.json @@ -54,6 +54,11 @@ "options_menu": { "graphics": "グラフィック", "graphics_menu": { + "window_mode": { + "entry": "画面表示:", + "windowed": "ウィンドウ", + "fullscreen": "フルスクリーン" + }, "lighting_effects": "ライティング効果:", "weapon_light_cone": "兵器のライトコーン:", "screen_shake": { diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index 20ec807..7fedade 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -30,6 +30,7 @@ use crate::framework::keyboard::ScanCode; use crate::framework::render_opengl::{GLContext, OpenGLRenderer}; use crate::framework::ui::init_imgui; use crate::graphics::VSyncMode; +use crate::shared_game_state::WindowMode; use crate::Game; use crate::GameError::RenderError; use crate::GAME_SUSPENDED; @@ -139,6 +140,7 @@ struct SDL2Context { window: WindowOrCanvas, gl_context: Option, blend_mode: sdl2::render::BlendMode, + fullscreen_type: sdl2::video::FullscreenType, } impl SDL2EventLoop { @@ -167,6 +169,7 @@ impl SDL2EventLoop { window: WindowOrCanvas::Win(window), gl_context: None, blend_mode: sdl2::render::BlendMode::Blend, + fullscreen_type: sdl2::video::FullscreenType::Off, })), opengl_available: RefCell::new(opengl_available), }; @@ -261,15 +264,25 @@ impl BackendEventLoop for SDL2EventLoop { if keymod.intersects(keyboard::Mod::RALTMOD | keyboard::Mod::LALTMOD) && drs_scan == ScanCode::Return { + let new_mode = match state.settings.window_mode { + WindowMode::Windowed => WindowMode::Fullscreen, + WindowMode::Fullscreen => WindowMode::Windowed, + }; + let fullscreen_type = new_mode.get_sdl2_fullscreen_type(); + let mut refs = self.refs.borrow_mut(); let window = refs.window.window_mut(); - if window.fullscreen_state() == sdl2::video::FullscreenType::Desktop { - window.set_fullscreen(sdl2::video::FullscreenType::Off); - window.subsystem().sdl().mouse().show_cursor(true); - } else { - window.set_fullscreen(sdl2::video::FullscreenType::Desktop); - window.subsystem().sdl().mouse().show_cursor(false); - } + + window.set_fullscreen(fullscreen_type); + window + .subsystem() + .sdl() + .mouse() + .show_cursor(new_mode.should_display_mouse_cursor()); + + refs.fullscreen_type = fullscreen_type; + + state.settings.window_mode = new_mode; } } ctx.keyboard_context.set_key(drs_scan, true); @@ -297,6 +310,24 @@ impl BackendEventLoop for SDL2EventLoop { } } + { + if state.settings.window_mode.get_sdl2_fullscreen_type() != self.refs.borrow().fullscreen_type { + let mut refs = self.refs.borrow_mut(); + let window = refs.window.window_mut(); + + let fullscreen_type = state.settings.window_mode.get_sdl2_fullscreen_type(); + + window.set_fullscreen(fullscreen_type); + window + .subsystem() + .sdl() + .mouse() + .show_cursor(state.settings.window_mode.should_display_mouse_cursor()); + + refs.fullscreen_type = fullscreen_type; + } + } + game.update(ctx).unwrap(); if let Some(_) = &state.next_scene { diff --git a/src/menu/settings_menu.rs b/src/menu/settings_menu.rs index e103fc8..594278a 100644 --- a/src/menu/settings_menu.rs +++ b/src/menu/settings_menu.rs @@ -9,7 +9,7 @@ use crate::input::combined_menu_controller::CombinedMenuController; use crate::menu::MenuEntry; use crate::menu::{Menu, MenuSelectionResult}; use crate::scene::title_scene::TitleScene; -use crate::shared_game_state::{Language, ScreenShakeIntensity, SharedGameState, TimingMode}; +use crate::shared_game_state::{Language, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode}; use crate::sound::InterpolationMode; use crate::{graphics, VSyncMode}; @@ -48,6 +48,23 @@ impl SettingsMenu { } pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + #[cfg(not(target_os = "android"))] + self.graphics.push_entry(MenuEntry::Options( + state.t("menus.options_menu.graphics_menu.window_mode.entry"), + state.settings.window_mode as usize, + vec![ + state.t("menus.options_menu.graphics_menu.window_mode.windowed"), + state.t("menus.options_menu.graphics_menu.window_mode.fullscreen"), + ], + )); + + #[cfg(target_os = "android")] + { + let entry_text = state.t("menus.options_menu.graphics_menu.window_mode.entry") + " N/A"; + self.graphics.push_entry(MenuEntry::Disabled(entry_text)); + self.graphics.selected += 1; + } + self.graphics.push_entry(MenuEntry::DescriptiveOptions( state.t("menus.options_menu.graphics_menu.vsync_mode.entry"), state.settings.vsync_mode as usize, @@ -291,7 +308,23 @@ impl SettingsMenu { _ => (), }, CurrentMenu::GraphicsMenu => match self.graphics.tick(controller, state) { - MenuSelectionResult::Selected(0, toggle) | MenuSelectionResult::Right(0, toggle, _) => { + MenuSelectionResult::Selected(0, toggle) + | MenuSelectionResult::Right(0, toggle, _) + | MenuSelectionResult::Left(0, toggle, _) => { + if let MenuEntry::Options(_, value, _) = toggle { + let (new_mode, new_value) = match *value { + 0 => (WindowMode::Fullscreen, 1), + 1 => (WindowMode::Windowed, 0), + _ => unreachable!(), + }; + + *value = new_value; + state.settings.window_mode = new_mode; + + let _ = state.settings.save(ctx); + } + } + MenuSelectionResult::Selected(1, toggle) | MenuSelectionResult::Right(1, toggle, _) => { if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle { let (new_mode, new_value) = match *value { 0 => (VSyncMode::VSync, 1), @@ -308,7 +341,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Left(0, toggle, _) => { + MenuSelectionResult::Left(1, toggle, _) => { if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle { let (new_mode, new_value) = match *value { 0 => (VSyncMode::VRRTickSync3x, 4), @@ -325,7 +358,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(1, toggle) => { + MenuSelectionResult::Selected(2, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.shader_effects = !state.settings.shader_effects; let _ = state.settings.save(ctx); @@ -333,7 +366,7 @@ impl SettingsMenu { *value = state.settings.shader_effects; } } - MenuSelectionResult::Selected(2, toggle) => { + MenuSelectionResult::Selected(3, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.light_cone = !state.settings.light_cone; let _ = state.settings.save(ctx); @@ -341,7 +374,7 @@ impl SettingsMenu { *value = state.settings.light_cone; } } - MenuSelectionResult::Selected(3, toggle) | MenuSelectionResult::Right(3, toggle, _) => { + MenuSelectionResult::Selected(4, toggle) | MenuSelectionResult::Right(4, toggle, _) => { if let MenuEntry::Options(_, value, _) = toggle { let (new_intensity, new_value) = match *value { 0 => (ScreenShakeIntensity::Half, 1), @@ -355,7 +388,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Left(3, toggle, _) => { + MenuSelectionResult::Left(4, toggle, _) => { if let MenuEntry::Options(_, value, _) = toggle { let (new_intensity, new_value) = match *value { 0 => (ScreenShakeIntensity::Off, 2), @@ -369,7 +402,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(4, toggle) => { + MenuSelectionResult::Selected(5, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.motion_interpolation = !state.settings.motion_interpolation; let _ = state.settings.save(ctx); @@ -377,7 +410,7 @@ impl SettingsMenu { *value = state.settings.motion_interpolation; } } - MenuSelectionResult::Selected(5, toggle) => { + MenuSelectionResult::Selected(6, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.subpixel_coords = !state.settings.subpixel_coords; let _ = state.settings.save(ctx); @@ -385,7 +418,7 @@ impl SettingsMenu { *value = state.settings.subpixel_coords; } } - MenuSelectionResult::Selected(6, toggle) => { + MenuSelectionResult::Selected(7, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.original_textures = !state.settings.original_textures; if self.on_title { @@ -398,7 +431,7 @@ impl SettingsMenu { *value = state.settings.original_textures; } } - MenuSelectionResult::Selected(7, toggle) => { + MenuSelectionResult::Selected(8, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.seasonal_textures = !state.settings.seasonal_textures; state.reload_graphics(); @@ -407,7 +440,7 @@ impl SettingsMenu { *value = state.settings.seasonal_textures; } } - MenuSelectionResult::Selected(9, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(10, _) | MenuSelectionResult::Canceled => { self.current = CurrentMenu::MainMenu } _ => (), diff --git a/src/settings.rs b/src/settings.rs index 066fd00..e1c3f67 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -7,7 +7,7 @@ use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; use crate::input::touch_player_controller::TouchPlayerController; use crate::player::TargetPlayer; -use crate::shared_game_state::{Language, ScreenShakeIntensity, TimingMode}; +use crate::shared_game_state::{Language, ScreenShakeIntensity, TimingMode, WindowMode}; use crate::sound::InterpolationMode; #[derive(serde::Serialize, serde::Deserialize)] @@ -48,6 +48,8 @@ pub struct Settings { pub debug_outlines: bool, pub fps_counter: bool, pub locale: Language, + #[serde(default = "default_window_mode")] + pub window_mode: WindowMode, #[serde(default = "default_vsync")] pub vsync_mode: VSyncMode, #[serde(default = "default_screen_shake_intensity")] @@ -63,7 +65,7 @@ fn default_true() -> bool { #[inline(always)] fn current_version() -> u32 { - 10 + 11 } #[inline(always)] @@ -71,6 +73,11 @@ fn default_timing() -> TimingMode { TimingMode::_50Hz } +#[inline(always)] +fn default_window_mode() -> WindowMode { + WindowMode::Windowed +} + #[inline(always)] fn default_interpolation() -> InterpolationMode { InterpolationMode::Linear @@ -158,6 +165,11 @@ impl Settings { self.screen_shake_intensity = default_screen_shake_intensity(); } + if self.version == 10 { + self.version = 11; + self.window_mode = default_window_mode(); + } + if self.version != initial_version { log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version); } @@ -209,6 +221,7 @@ impl Default for Settings { debug_outlines: false, fps_counter: false, locale: Language::English, + window_mode: WindowMode::Windowed, vsync_mode: VSyncMode::VSync, screen_shake_intensity: ScreenShakeIntensity::Full, debug_mode: false, diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 73a9ff6..7a076ee 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -70,6 +70,29 @@ impl TimingMode { } } +#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum WindowMode { + Windowed, + Fullscreen, +} + +impl WindowMode { + #[cfg(feature = "backend-sdl")] + pub fn get_sdl2_fullscreen_type(&self) -> sdl2::video::FullscreenType { + match self { + WindowMode::Windowed => sdl2::video::FullscreenType::Off, + WindowMode::Fullscreen => sdl2::video::FullscreenType::Desktop, + } + } + + pub fn should_display_mouse_cursor(&self) -> bool { + match self { + WindowMode::Windowed => true, + WindowMode::Fullscreen => false, + } + } +} + #[derive(PartialEq, Eq, Copy, Clone, num_derive::FromPrimitive)] pub enum GameDifficulty { Normal = 0,