mod bag; mod buttons; mod character_window; mod chat; mod esc_menu; mod img_ids; mod map; mod minimap; mod quest; mod settings_window; mod skillbar; mod social; mod spell; pub use settings_window::ScaleChange; use bag::Bag; use buttons::Buttons; use character_window::CharacterWindow; use chat::Chat; use esc_menu::EscMenu; use img_ids::Imgs; use map::Map; use minimap::MiniMap; use quest::Quest; use serde::{Deserialize, Serialize}; use settings_window::{SettingsTab, SettingsWindow}; use skillbar::Skillbar; use social::{Social, SocialTab}; use spell::Spell; use crate::{ render::{Consts, Globals, Renderer}, scene::camera::Camera, settings::ControlSettings, ui::{Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, GameInput}, GlobalState, }; use client::{Client, Event as ClientEvent}; use common::{comp, terrain::TerrainChunkSize, vol::VolSize}; use conrod_core::{ text::cursor::Index, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; use specs::Join; use std::collections::VecDeque; use vek::*; #[cfg(feature = "discord")] use crate::{discord, discord::DiscordUpdate}; const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0); const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); const TEXT_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); const TEXT_COLOR_3: Color = Color::Rgba(1.0, 1.0, 1.0, 0.1); const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0); const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0); const META_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0); const TELL_COLOR: Color = Color::Rgba(0.98, 0.71, 1.0, 1.0); const PRIVATE_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0); // Difference between private and tell? const BROADCAST_COLOR: Color = Color::Rgba(0.28, 0.83, 0.71, 1.0); const GAME_UPDATE_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0); const SAY_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); const GROUP_COLOR: Color = Color::Rgba(0.47, 0.84, 1.0, 1.0); const FACTION_COLOR: Color = Color::Rgba(0.24, 1.0, 0.48, 1.0); const KILL_COLOR: Color = Color::Rgba(1.0, 0.17, 0.17, 1.0); widget_ids! { struct Ids { // Crosshair crosshair_inner, crosshair_outer, // Character Names name_tags[], // Health Bars health_bars[], health_bar_backs[], // Test bag_space_add, // Debug debug_bg, fps_counter, ping, coordinates, velocity, loaded_distance, // Game Version version, // Help help, help_bg, // Window Frames window_frame_0, window_frame_1, window_frame_2, window_frame_3, window_frame_4, window_frame_5, // Contents button_help2, // External chat, map, character_window, minimap, bag, social, quest, spell, skillbar, buttons, esc_menu, small_window, social_window, settings_window, } } font_ids! { pub struct Fonts { opensans: "voxygen.font.OpenSans-Regular", metamorph: "voxygen.font.Metamorphous-Regular", } } pub struct DebugInfo { pub tps: f64, pub ping_ms: f64, pub coordinates: Option, pub velocity: Option, } pub enum Event { SendMessage(String), AdjustMousePan(u32), AdjustMouseZoom(u32), AdjustViewDistance(u32), AdjustVolume(f32), ChangeAudioDevice(String), ChangeMaxFPS(u32), ChangeFOV(u16), CrosshairTransp(f32), CrosshairType(CrosshairType), UiScale(ScaleChange), CharacterSelection, SwapInventorySlots(usize, usize), DropInventorySlot(usize), Logout, Quit, } // TODO: Are these the possible layouts we want? // TODO: Maybe replace this with bitflags. // `map` is not here because it currently is displayed over the top of other open windows. #[derive(PartialEq)] pub enum Windows { Settings, // Display settings window. None, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum CrosshairType { Round, RoundEdges, Edges, } pub struct Show { ui: bool, help: bool, debug: bool, bag: bool, social: bool, spell: bool, quest: bool, character_window: bool, esc_menu: bool, open_windows: Windows, map: bool, inventory_test_button: bool, mini_map: bool, ingame: bool, settings_tab: SettingsTab, social_tab: SocialTab, want_grab: bool, } impl Show { fn bag(&mut self, open: bool) { self.bag = open; self.want_grab = !open; } fn toggle_bag(&mut self) { self.bag(!self.bag); } fn map(&mut self, open: bool) { self.map = open; self.bag = false; self.want_grab = !open; } fn character_window(&mut self, open: bool) { self.character_window = open; self.bag = false; self.want_grab = !open; } fn social(&mut self, open: bool) { self.social = open; self.spell = false; self.quest = false; self.want_grab = !open; } fn spell(&mut self, open: bool) { self.social = false; self.spell = open; self.quest = false; self.want_grab = !open; } fn quest(&mut self, open: bool) { self.social = false; self.spell = false; self.quest = open; self.want_grab = !open; } fn toggle_map(&mut self) { self.map(!self.map) } fn toggle_mini_map(&mut self) { self.mini_map = !self.mini_map; } fn toggle_char_window(&mut self) { self.character_window = !self.character_window } fn settings(&mut self, open: bool) { self.open_windows = if open { Windows::Settings } else { Windows::None }; self.bag = false; self.social = false; self.spell = false; self.quest = false; self.want_grab = !open; } fn toggle_settings(&mut self) { match self.open_windows { Windows::Settings => self.settings(false), _ => self.settings(true), }; } fn toggle_help(&mut self) { self.help = !self.help } fn toggle_ui(&mut self) { self.ui = !self.ui; } fn toggle_windows(&mut self) { if self.bag || self.esc_menu || self.map || self.social || self.quest || self.spell || self.character_window || match self.open_windows { Windows::None => false, _ => true, } { self.bag = false; self.esc_menu = false; self.map = false; self.social = false; self.quest = false; self.spell = false; self.character_window = false; self.open_windows = Windows::None; self.want_grab = true; } else { self.esc_menu = true; self.want_grab = false; } } fn open_setting_tab(&mut self, tab: SettingsTab) { self.open_windows = Windows::Settings; self.esc_menu = false; self.settings_tab = tab; self.bag = false; self.want_grab = false; } fn toggle_social(&mut self) { self.social = !self.social; self.spell = false; self.quest = false; } fn open_social_tab(&mut self, social_tab: SocialTab) { self.social_tab = social_tab; self.spell = false; self.quest = false; } fn toggle_spell(&mut self) { self.spell = !self.spell; self.social = false; self.quest = false; } fn toggle_quest(&mut self) { self.quest = !self.quest; self.spell = false; self.social = false; } } pub struct Hud { ui: Ui, ids: Ids, imgs: Imgs, fonts: Fonts, new_messages: VecDeque, inventory_space: usize, show: Show, to_focus: Option>, force_ungrab: bool, force_chat_input: Option, force_chat_cursor: Option, } impl Hud { pub fn new(global_state: &mut GlobalState) -> Self { let window = &mut global_state.window; let settings = &global_state.settings; let mut ui = Ui::new(window).unwrap(); ui.set_scaling_mode(settings.gameplay.ui_scale); // Generate ids. let ids = Ids::new(ui.id_generator()); // Load images. let imgs = Imgs::load(&mut ui).expect("Failed to load images!"); // Load fonts. let fonts = Fonts::load(&mut ui).expect("Failed to load fonts!"); Self { ui, imgs, fonts, ids, new_messages: VecDeque::new(), inventory_space: 8, show: Show { help: false, debug: true, bag: false, esc_menu: false, open_windows: Windows::None, map: false, ui: true, social: false, quest: false, spell: false, character_window: false, inventory_test_button: false, mini_map: false, settings_tab: SettingsTab::Interface, social_tab: SocialTab::Online, want_grab: true, ingame: true, }, to_focus: None, force_ungrab: false, force_chat_input: None, force_chat_cursor: None, } } fn update_layout( &mut self, client: &Client, global_state: &GlobalState, debug_info: DebugInfo, ) -> Vec { let mut events = Vec::new(); let ref mut ui_widgets = self.ui.set_widgets().0; let version = format!("{}-{}", env!("CARGO_PKG_VERSION"), common::util::GIT_HASH); if self.show.ingame { // Crosshair Image::new( // TODO: Do we want to match on this every frame? match global_state.settings.gameplay.crosshair_type { CrosshairType::Round => self.imgs.crosshair_outer_round, CrosshairType::RoundEdges => self.imgs.crosshair_outer_round_edges, CrosshairType::Edges => self.imgs.crosshair_outer_edges, }, ) .w_h(21.0 * 1.5, 21.0 * 1.5) .middle_of(ui_widgets.window) .color(Some(Color::Rgba( 1.0, 1.0, 1.0, global_state.settings.gameplay.crosshair_transp, ))) .set(self.ids.crosshair_outer, ui_widgets); Image::new(self.imgs.crosshair_inner) .w_h(21.0 * 2.0, 21.0 * 2.0) .middle_of(self.ids.crosshair_outer) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.6))) .set(self.ids.crosshair_inner, ui_widgets); // Nametags and healthbars let ecs = client.state().ecs(); let pos = ecs.read_storage::(); let stats = ecs.read_storage::(); let players = ecs.read_storage::(); let scales = ecs.read_storage::(); let entities = ecs.entities(); let me = client.entity(); let view_distance = client.view_distance().unwrap_or(1); // Get player position. let player_pos = client .state() .ecs() .read_storage::() .get(client.entity()) .map_or(Vec3::zero(), |pos| pos.0); let mut name_id_walker = self.ids.name_tags.walk(); let mut health_id_walker = self.ids.health_bars.walk(); let mut health_back_id_walker = self.ids.health_bar_backs.walk(); // Render Name Tags for (pos, name, scale) in (&entities, &pos, &stats, players.maybe(), scales.maybe()) .join() .filter(|(entity, _, stats, _, _)| *entity != me && !stats.is_dead) // Don't process nametags outside the vd (visibility further limited by ui backend) .filter(|(_, pos, _, _, _)| { (pos.0 - player_pos) .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32) .magnitude() < view_distance as f32 }) .map(|(_, pos, stats, player, scale)| { // TODO: This is temporary // If the player used the default character name display their name instead let name = if stats.name == "Character Name" { player.map_or(&stats.name, |p| &p.alias) } else { &stats.name }; (pos.0, name, scale) }) { let scale = scale.map(|s| s.0).unwrap_or(1.0); let id = name_id_walker.next( &mut self.ids.name_tags, &mut ui_widgets.widget_id_generator(), ); Text::new(&name) .font_size(20) .color(Color::Rgba(0.61, 0.61, 0.89, 1.0)) .x_y(0.0, 0.0) .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) .resolution(100.0) .set(id, ui_widgets); } // Render Health Bars for (_entity, pos, stats, scale) in (&entities, &pos, &stats, scales.maybe()) .join() .filter(|(entity, _, stats, _)| { *entity != me && !stats.is_dead && stats.health.current() != stats.health.maximum() }) // Don't process health bars outside the vd (visibility further limited by ui backend) .filter(|(_, pos, _, _)| { (pos.0 - player_pos) .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32) .magnitude() < view_distance as f32 }) { let scale = scale.map(|s| s.0).unwrap_or(1.0); let back_id = health_back_id_walker.next( &mut self.ids.health_bar_backs, &mut ui_widgets.widget_id_generator(), ); let bar_id = health_id_walker.next( &mut self.ids.health_bars, &mut ui_widgets.widget_id_generator(), ); // Background Rectangle::fill_with([120.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5)) .x_y(0.0, -25.0) .position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) .resolution(100.0) .set(back_id, ui_widgets); // % HP Filling Rectangle::fill_with( [ 120.0 * (stats.health.current() as f64 / stats.health.maximum() as f64), 8.0, ], HP_COLOR, ) .x_y(0.0, -25.0) .position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) .resolution(100.0) .set(bar_id, ui_widgets); } } // Display debug window. if self.show.debug { // Alpha Version Text::new(&version) .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) .font_size(14) .font_id(self.fonts.opensans) .color(TEXT_COLOR) .set(self.ids.version, ui_widgets); // Ticks per second Text::new(&format!("FPS: {:.1}", debug_info.tps)) .color(TEXT_COLOR) .down_from(self.ids.version, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.fps_counter, ui_widgets); // Ping Text::new(&format!("Ping: {:.1}ms", debug_info.ping_ms)) .color(TEXT_COLOR) .down_from(self.ids.fps_counter, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.ping, ui_widgets); // Player's position let coordinates_text = match debug_info.coordinates { Some(coordinates) => format!("Coordinates: {:.1}", coordinates.0), None => "Player has no Pos component".to_owned(), }; Text::new(&coordinates_text) .color(TEXT_COLOR) .down_from(self.ids.ping, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.coordinates, ui_widgets); // Player's velocity let velocity_text = match debug_info.velocity { Some(velocity) => format!("Velocity: {:.1}", velocity.0), None => "Player has no Vel component".to_owned(), }; Text::new(&velocity_text) .color(TEXT_COLOR) .down_from(self.ids.coordinates, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.velocity, ui_widgets); // Loaded distance Text::new(&format!( "View distance: {} chunks", client.loaded_distance().unwrap_or(0) )) .color(TEXT_COLOR) .down_from(self.ids.velocity, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.loaded_distance, ui_widgets); } // Add Bag-Space Button. if self.show.inventory_test_button { if Button::image(self.imgs.button) .w_h(100.0, 100.0) .middle_of(ui_widgets.window) .label("Add 1 Space") .label_font_size(20) .label_color(TEXT_COLOR) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .set(self.ids.bag_space_add, ui_widgets) .was_clicked() { if self.inventory_space < 100 { self.inventory_space += 1; } else { } }; } // Help Text if self.show.help { Image::new(self.imgs.window_frame_2) .top_left_with_margins_on(ui_widgets.window, 3.0, 3.0) .w_h(300.0, 190.0) .set(self.ids.help_bg, ui_widgets); Text::new(get_help_text(&global_state.settings.controls).as_str()) .color(TEXT_COLOR) .top_left_with_margins_on(self.ids.help_bg, 20.0, 20.0) .font_id(self.fonts.opensans) .font_size(18) .set(self.ids.help, ui_widgets); // X-button if Button::image(self.imgs.close_button) .w_h(100.0 * 0.2, 100.0 * 0.2) .hover_image(self.imgs.close_button_hover) .press_image(self.imgs.close_button_press) .top_right_with_margins_on(self.ids.help_bg, 4.0, 4.0) .set(self.ids.button_help2, ui_widgets) .was_clicked() { self.show.help = false; }; } // Bag button and nearby icons match Buttons::new( &self.show.open_windows, self.show.map, self.show.bag, &self.imgs, &self.fonts, ) .set(self.ids.buttons, ui_widgets) { Some(buttons::Event::ToggleBag) => self.show.toggle_bag(), Some(buttons::Event::ToggleSettings) => self.show.toggle_settings(), Some(buttons::Event::ToggleCharacter) => self.show.toggle_char_window(), Some(buttons::Event::ToggleSocial) => self.show.toggle_social(), Some(buttons::Event::ToggleSpell) => self.show.toggle_spell(), Some(buttons::Event::ToggleQuest) => self.show.toggle_quest(), Some(buttons::Event::ToggleMap) => self.show.toggle_map(), None => {} } // MiniMap match MiniMap::new(&self.show, client, &self.imgs, &self.fonts) .set(self.ids.minimap, ui_widgets) { Some(minimap::Event::Toggle) => self.show.toggle_mini_map(), None => {} } // Bag contents if self.show.bag { match Bag::new(client, &self.imgs, &self.fonts).set(self.ids.bag, ui_widgets) { Some(bag::Event::HudEvent(event)) => events.push(event), Some(bag::Event::Close) => { self.show.bag(false); self.force_ungrab = true; } None => {} } } // Skillbar // Get player stats if let Some(stats) = client .state() .ecs() .read_storage::() .get(client.entity()) { Skillbar::new(&self.imgs, &self.fonts, stats).set(self.ids.skillbar, ui_widgets); } // Chat box let mut chat = Chat::new(&mut self.new_messages, &self.imgs, &self.fonts); if let Some(input) = self.force_chat_input.take() { chat = chat.input(input); } if let Some(pos) = self.force_chat_cursor.take() { chat = chat.cursor_pos(pos); } match chat.set(self.ids.chat, ui_widgets) { Some(chat::Event::SendMessage(message)) => { events.push(Event::SendMessage(message)); } Some(chat::Event::Focus(focus_id)) => { self.to_focus = Some(Some(focus_id)); } None => {} } self.new_messages = VecDeque::new(); // Windows // Char Window will always appear at the left side. Other Windows default to the // left side, but when the Char Window is opened they will appear to the right of it. // Settings if let Windows::Settings = self.show.open_windows { for event in SettingsWindow::new(&global_state, &self.show, &self.imgs, &self.fonts) .set(self.ids.settings_window, ui_widgets) { match event { settings_window::Event::ToggleHelp => self.show.toggle_help(), settings_window::Event::ToggleInventoryTestButton => { self.show.inventory_test_button = !self.show.inventory_test_button } settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug, settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab), settings_window::Event::Close => self.show.settings(false), settings_window::Event::AdjustMousePan(sensitivity) => { events.push(Event::AdjustMousePan(sensitivity)); } settings_window::Event::AdjustMouseZoom(sensitivity) => { events.push(Event::AdjustMouseZoom(sensitivity)); } settings_window::Event::AdjustViewDistance(view_distance) => { events.push(Event::AdjustViewDistance(view_distance)); } settings_window::Event::CrosshairTransp(crosshair_transp) => { events.push(Event::CrosshairTransp(crosshair_transp)); } settings_window::Event::AdjustVolume(volume) => { events.push(Event::AdjustVolume(volume)); } settings_window::Event::MaximumFPS(max_fps) => { events.push(Event::ChangeMaxFPS(max_fps)); } settings_window::Event::ChangeAudioDevice(name) => { events.push(Event::ChangeAudioDevice(name)); } settings_window::Event::CrosshairType(crosshair_type) => { events.push(Event::CrosshairType(crosshair_type)); } settings_window::Event::UiScale(scale_change) => { events.push(Event::UiScale(scale_change)); } settings_window::Event::AdjustFOV(new_fov) => { events.push(Event::ChangeFOV(new_fov)); } } } } // Social Window if self.show.social { for event in Social::new( /*&global_state,*/ &self.show, client, &self.imgs, &self.fonts, ) .set(self.ids.social_window, ui_widgets) { match event { social::Event::Close => self.show.social(false), social::Event::ChangeSocialTab(social_tab) => { self.show.open_social_tab(social_tab) } } } } // Character Window if self.show.character_window { let ecs = client.state().ecs(); let stats = ecs.read_storage::(); let player_stats = stats.get(client.entity()).unwrap(); match CharacterWindow::new(&self.show, &player_stats, &self.imgs, &self.fonts) .set(self.ids.character_window, ui_widgets) { Some(character_window::Event::Close) => { self.show.character_window(false); self.force_ungrab = true; } None => {} } } // Spellbook if self.show.spell { match Spell::new(&self.show, client, &self.imgs, &self.fonts) .set(self.ids.spell, ui_widgets) { Some(spell::Event::Close) => { self.show.spell(false); self.force_ungrab = true; } None => {} } } // Quest Log if self.show.quest { match Quest::new(&self.show, client, &self.imgs, &self.fonts) .set(self.ids.quest, ui_widgets) { Some(quest::Event::Close) => { self.show.quest(false); self.force_ungrab = true; } None => {} } } // Map if self.show.map { match Map::new(&self.show, client, &self.imgs, &self.fonts) .set(self.ids.map, ui_widgets) { Some(map::Event::Close) => { self.show.map(false); self.force_ungrab = true; } None => {} } } if self.show.esc_menu { match EscMenu::new(&self.imgs, &self.fonts).set(self.ids.esc_menu, ui_widgets) { Some(esc_menu::Event::OpenSettings(tab)) => { self.show.open_setting_tab(tab); } Some(esc_menu::Event::Close) => { self.show.esc_menu = false; self.show.want_grab = false; self.force_ungrab = true; } Some(esc_menu::Event::Logout) => { events.push(Event::Logout); #[cfg(feature = "discord")] { discord::send_all(vec![ DiscordUpdate::Details("Menu".into()), DiscordUpdate::State("Idling".into()), DiscordUpdate::LargeImg("bg_main".into()), ]); } } Some(esc_menu::Event::Quit) => events.push(Event::Quit), Some(esc_menu::Event::CharacterSelection) => events.push(Event::CharacterSelection), None => {} } } events } pub fn new_message(&mut self, msg: ClientEvent) { self.new_messages.push_back(msg); } pub fn scale_change(&mut self, scale_change: ScaleChange) -> ScaleMode { let scale_mode = match scale_change { ScaleChange::Adjust(scale) => ScaleMode::Absolute(scale), ScaleChange::ToAbsolute => self.ui.scale().scaling_mode_as_absolute(), ScaleChange::ToRelative => self.ui.scale().scaling_mode_as_relative(), }; self.ui.set_scaling_mode(scale_mode); scale_mode } // Checks if a TextEdit widget has the keyboard captured. fn typing(&self) -> bool { if let Some(id) = self.ui.widget_capturing_keyboard() { self.ui .widget_graph() .widget(id) .filter(|c| { c.type_id == std::any::TypeId::of::<::State>() }) .is_some() } else { false } } pub fn handle_event(&mut self, event: WinEvent, global_state: &mut GlobalState) -> bool { let cursor_grabbed = global_state.window.is_cursor_grabbed(); let handled = match event { WinEvent::Ui(event) => { if (self.typing() && event.is_keyboard() && self.show.ui) || !(cursor_grabbed && event.is_keyboard_or_mouse()) { self.ui.handle_event(event); } true } WinEvent::InputUpdate(GameInput::ToggleInterface, true) if !self.typing() => { self.show.toggle_ui(); true } WinEvent::InputUpdate(GameInput::ToggleCursor, true) if !self.typing() => { self.force_ungrab = !self.force_ungrab; true } _ if !self.show.ui => false, WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(), WinEvent::InputUpdate(GameInput::Enter, true) => { self.ui.focus_widget(if self.typing() { None } else { Some(self.ids.chat) }); true } WinEvent::InputUpdate(GameInput::Escape, true) => { if self.typing() { self.ui.focus_widget(None); } else { // Close windows on esc self.show.toggle_windows(); } true } // Press key while not typing WinEvent::InputUpdate(key, true) if !self.typing() => match key { GameInput::Command => { self.force_chat_input = Some("/".to_owned()); self.force_chat_cursor = Some(Index { line: 0, char: 1 }); self.ui.focus_widget(Some(self.ids.chat)); true } GameInput::Map => { self.show.toggle_map(); true } GameInput::Bag => { self.show.toggle_bag(); true } GameInput::QuestLog => { self.show.toggle_quest(); true } GameInput::CharacterWindow => { self.show.toggle_char_window(); true } GameInput::Social => { self.show.toggle_social(); true } GameInput::Spellbook => { self.show.toggle_spell(); true } GameInput::Settings => { self.show.toggle_settings(); true } GameInput::Help => { self.show.toggle_help(); true } GameInput::ToggleDebug => { self.show.debug = !self.show.debug; true } GameInput::ToggleIngameUi => { self.show.ingame = !self.show.ingame; true } _ => false, }, // Else the player is typing in chat WinEvent::InputUpdate(_key, _) => self.typing(), WinEvent::Char(_) => self.typing(), WinEvent::Focused(state) => { self.force_ungrab = !state; true } _ => false, }; // Handle cursor grab. global_state .window .grab_cursor(!self.force_ungrab && self.show.want_grab); handled } pub fn maintain( &mut self, client: &Client, global_state: &mut GlobalState, debug_info: DebugInfo, camera: &Camera, ) -> Vec { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } let events = self.update_layout(client, global_state, debug_info); let (view_mat, _, _) = camera.compute_dependents(client); let fov = camera.get_fov(); self.ui.maintain( &mut global_state.window.renderer_mut(), Some((view_mat, fov)), ); events } pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { // Don't show anything if the UI is toggled off. if self.show.ui { self.ui.render(renderer, Some(globals)); } } } // Get the text to show in the help window and use the // length of the longest line to resize the window. fn get_help_text(cs: &ControlSettings) -> String { format!( "{free_cursor:?} = Free cursor\n\ {escape:?} = Open/close menus\n\ \n\ {help:?} = Toggle this window\n\ {toggle_interface:?} = Toggle interface\n\ \n\ {chat:?} = Open chat\n\ Mouse Wheel = Scroll chat/zoom", free_cursor = cs.toggle_cursor, escape = cs.escape, help = cs.help, toggle_interface = cs.toggle_interface, chat = cs.enter ) }