Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / client / src / app / input.rs

use common::protocol::Packet;
use common::stats::ACHIEVEMENT;
use winit::event::MouseButton;
use winit::keyboard::KeyCode;
use winit::window::{CursorGrabMode, Fullscreen};

use super::Game;
use crate::interaction::Screen;
use crate::menu::Page;
use crate::settings::Binding;

impl Game {
    pub(super) fn set_grab(&mut self, grab: bool) {
        let grab = grab && self.focused && self.session.is_some();
        if grab == self.view.grabbed {
            return;
        }
        let window = &self.view.window;
        let result = if grab {
            window
                .set_cursor_grab(CursorGrabMode::Locked)
                .or_else(|_| window.set_cursor_grab(CursorGrabMode::Confined))
        } else if !self.focused {
            window.set_cursor_grab(CursorGrabMode::None)
        } else {
            // `Mouse.setGrabbed(false)` leaves the cursor in the middle of the window. winit only
            // hints the position of a locked pointer on Wayland, which compositors may ignore, so
            // the pointer is warped after the release where supported
            let size = window.inner_size();
            let center = [size.width as f32 / 2.0, size.height as f32 / 2.0];
            let position = winit::dpi::PhysicalPosition::new(center[0], center[1]);
            let hinted = window.set_cursor_position(position);
            let result = window.set_cursor_grab(CursorGrabMode::None);
            let warped = self.view.pointer_warp.as_mut().is_some_and(|warp| {
                let position = position.to_logical(window.scale_factor());
                warp.warp(position.x, position.y)
                    .inspect_err(|error| tracing::warn!("cannot warp the cursor: {error}"))
                    .unwrap_or(false)
            });
            match hinted {
                Err(error) if !warped => tracing::warn!("cannot center the cursor: {error}"),
                _ => {
                    self.view.cursor = center;
                    if let Some(session) = &mut self.session {
                        session.interaction.cursor = center;
                    }
                }
            }
            result
        };
        if let Err(error) = result {
            tracing::warn!("cannot change cursor grab: {error}");
            return;
        }
        window.set_cursor_visible(!grab);
        self.view.grabbed = grab;
        if !grab && let Some(session) = &mut self.session {
            session.player.keys = Default::default();
            session.interaction.shift = false;
            session.interaction.cancel(&session.connection);
        }
    }

    pub(super) fn focus(&mut self) {
        if let Some(session) = &self.session {
            session.backend.set_activity(match self.menu.page {
                Some(Page::Loading) => server::Activity::Preparing,
                Some(_) => server::Activity::Paused,
                None => server::Activity::Running,
            });
            if self.menu.page.is_some() || session.interaction.screen.is_some() {
                self.set_grab(false);
            }
        } else {
            self.set_grab(false);
        }
        let text = self.menu.field_rects(self.gui_space().0)[0].is_some();
        if self.ime_enabled != text {
            self.view.window.set_ime_allowed(text);
            self.ime_enabled = text;
        }
    }

    pub(super) fn mouse(&mut self, button: MouseButton, pressed: bool) {
        if self.menu.page.is_some() {
            if button != MouseButton::Left {
                return;
            }
            if !pressed {
                if self.menu.mouse_up() {
                    self.apply_options(true);
                }
                return;
            }
            let (size, cursor) = self.gui_space();
            if let Some(action) = self.menu.click(cursor, size, &mut self.options) {
                self.audio.click();
                self.action(action);
            }
            return;
        }
        let (size, cursor) = self.gui_space();
        let Some(session) = &mut self.session else {
            return;
        };
        if let Some(screen) = session.interaction.screen {
            if !pressed {
                return;
            }
            if screen == Screen::Inventory {
                if let Some(slot) =
                    crate::gui::slot_at(size, session.interaction.container.kind, cursor)
                    && matches!(button, MouseButton::Left | MouseButton::Right)
                {
                    let right = i8::from(button == MouseButton::Right);
                    session
                        .interaction
                        .click_slot(slot, right, &session.connection);
                }
            } else if screen == Screen::Death && button == MouseButton::Left {
                match crate::gui::button_at(size, screen, cursor) {
                    Some(0) => {
                        session.connection.send(Packet::Respawn { dimension: 0 });
                        session.interaction.screen = None;
                        self.audio.click();
                        self.set_grab(true);
                    }
                    Some(1) => {
                        self.audio.click();
                        self.leave();
                    }
                    _ => {}
                }
            }
        } else if !self.view.grabbed && pressed && button == MouseButton::Left {
            self.set_grab(true);
        } else if self.view.grabbed {
            match button {
                MouseButton::Left => session.interaction.attack_button(pressed),
                MouseButton::Right => session.interaction.use_button(pressed),
                MouseButton::Middle if pressed => session
                    .interaction
                    .pick_block(&session.world, &session.connection),
                _ => {}
            }
        }
    }

    pub(super) fn key(&mut self, code: KeyCode, pressed: bool, repeat: bool, text: Option<&str>) {
        // Shortcuts that do not consume the key: in game it may also be bound to movement
        if pressed
            && !repeat
            && matches!(code, KeyCode::F2 | KeyCode::F11)
            && self.menu.binding.is_none()
        {
            if code == KeyCode::F2 {
                self.view.renderer.request_screenshot();
            } else {
                let window = &self.view.window;
                let fullscreen = Fullscreen::Borderless(None);
                window.set_fullscreen(window.fullscreen().is_none().then_some(fullscreen));
            }
            if self.menu.page.is_some() {
                return;
            }
        }
        if self.menu.page.is_some() {
            if !pressed
                || (repeat
                    && (self.menu.binding.is_some()
                        || matches!(
                            code,
                            KeyCode::Enter | KeyCode::NumpadEnter | KeyCode::Escape | KeyCode::Tab
                        )))
            {
                return;
            }
            if self.modifiers.control_key()
                && self.menu.binding.is_none()
                && let Some(field) = self.menu.fields.iter_mut().find(|field| field.focused)
                && matches!(code, KeyCode::KeyV | KeyCode::KeyC | KeyCode::KeyX)
            {
                let clipboard = self.clipboard.as_mut();
                if code == KeyCode::KeyV {
                    if let Some(Ok(text)) = clipboard.map(arboard::Clipboard::get_text) {
                        field.insert(&text);
                    }
                } else {
                    if let Some(clipboard) = clipboard {
                        let _ = clipboard.set_text(field.text[field.selection()].to_owned());
                    }
                    if code == KeyCode::KeyX {
                        field.insert("");
                    }
                }
                self.update_folder();
                return;
            }
            let binding = self.menu.binding.is_some();
            let action = self.menu.key(
                code,
                text,
                self.modifiers.control_key(),
                self.modifiers.shift_key(),
                self.gui_space().0,
                &mut self.options,
            );
            if binding {
                self.apply_options(true);
            }
            self.update_folder();
            if let Some(action) = action {
                self.audio.click();
                self.action(action);
            }
            return;
        }
        if repeat {
            return;
        }
        let Some(session) = &mut self.session else {
            return;
        };
        if matches!(code, KeyCode::ShiftLeft | KeyCode::ShiftRight) {
            self.shift_keys[usize::from(code == KeyCode::ShiftRight)] = pressed;
            session.interaction.shift = self.shift_keys.contains(&true);
        }
        if pressed && (code == KeyCode::Escape || self.options.matches(Binding::Inventory, code)) {
            match session.interaction.screen {
                Some(Screen::Death) => {}
                Some(_) => {
                    session.interaction.close(&session.connection);
                    self.set_grab(true);
                }
                None if code == KeyCode::Escape => self.open(Page::Pause),
                None => {
                    session.interaction.screen = Some(Screen::Inventory);
                    self.statistics.stats.lock().unwrap().add(ACHIEVEMENT, 1);
                    self.focus();
                }
            }
            return;
        }
        if !self.view.grabbed || session.interaction.screen.is_some() {
            return;
        }
        if pressed {
            match code {
                KeyCode::F1 => session.hide_gui = !session.hide_gui,
                KeyCode::F3 => session.show_debug = !session.show_debug,
                KeyCode::F5 => session.player.third_person = !session.player.third_person,
                KeyCode::F8 => session.smooth_camera = !session.smooth_camera,
                _ => {}
            }
            // The LWJGL codes of the keys 1 to 9
            if let Some(key @ 2..=10) = crate::settings::key_code(code) {
                session
                    .interaction
                    .select(key as usize - 2, &session.connection);
            }
            if self.options.matches(Binding::Drop, code) {
                session.interaction.drop_selected(&session.connection);
            }
            // `GuiChat` only exists in multiplayer
            if self.options.matches(Binding::Chat, code) && !session.backend.local() {
                self.open(Page::Chat);
                return;
            }
        }
        let keys = &mut session.player.keys;
        for (binding, key) in [
            (Binding::Forward, &mut keys.forward),
            (Binding::Back, &mut keys.back),
            (Binding::Left, &mut keys.left),
            (Binding::Right, &mut keys.right),
            (Binding::Jump, &mut keys.jump),
            (Binding::Sneak, &mut keys.sneak),
        ] {
            if self.options.matches(binding, code) {
                *key = pressed;
            }
        }
        if pressed && self.options.matches(Binding::Fog, code) {
            let distance = self.options.render_distance;
            self.options.render_distance = if self.modifiers.shift_key() {
                distance.farther()
            } else {
                distance.shorter()
            };
            self.apply_options(true);
        }
    }
}