From 95a6e35a3a9a97bc14ef04464b1b74702c596e44 Mon Sep 17 00:00:00 2001 From: hqurve Date: Fri, 14 May 2021 12:27:15 +0000 Subject: [PATCH] Added chat tabs --- CHANGELOG.md | 1 + .../settings/buttons/settings_button_plus.png | 3 + .../buttons/settings_button_plus_hover.png | 3 + .../buttons/settings_button_plus_press.png | 3 + .../ui/settings/chat_tab_settings_bg.png | 3 + .../ui/settings/chat_tab_settings_frame.png | 3 + assets/voxygen/i18n/en/common.ron | 2 + assets/voxygen/i18n/en/hud/chat.ron | 3 + assets/voxygen/i18n/en/hud/hud_settings.ron | 17 + voxygen/src/hud/chat.rs | 366 +++++++--- voxygen/src/hud/img_ids.rs | 7 + voxygen/src/hud/mod.rs | 53 +- voxygen/src/hud/settings_window/chat.rs | 658 ++++++++++++++++++ voxygen/src/hud/settings_window/interface.rs | 71 -- voxygen/src/hud/settings_window/mod.rs | 23 + voxygen/src/session/settings_change.rs | 67 +- voxygen/src/settings/chat.rs | 88 +++ voxygen/src/settings/interface.rs | 4 - voxygen/src/settings/mod.rs | 20 +- 19 files changed, 1177 insertions(+), 218 deletions(-) create mode 100644 assets/voxygen/element/ui/settings/buttons/settings_button_plus.png create mode 100644 assets/voxygen/element/ui/settings/buttons/settings_button_plus_hover.png create mode 100644 assets/voxygen/element/ui/settings/buttons/settings_button_plus_press.png create mode 100644 assets/voxygen/element/ui/settings/chat_tab_settings_bg.png create mode 100644 assets/voxygen/element/ui/settings/chat_tab_settings_frame.png create mode 100644 voxygen/src/hud/settings_window/chat.rs create mode 100644 voxygen/src/settings/chat.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d0b2e082d8..006b3a95ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - /skill_preset command which allows you to apply skill presets - Added timed bans and ban history. - Added non-admin moderators with limit privileges and updated the security model to reflect this. +- Chat tabs ### Changed diff --git a/assets/voxygen/element/ui/settings/buttons/settings_button_plus.png b/assets/voxygen/element/ui/settings/buttons/settings_button_plus.png new file mode 100644 index 0000000000..a3070dfe56 --- /dev/null +++ b/assets/voxygen/element/ui/settings/buttons/settings_button_plus.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba7f52ff69aff66fd574f1cb35cca764e6e0dcae4e1d8962943bfa12ae8e635a +size 586 diff --git a/assets/voxygen/element/ui/settings/buttons/settings_button_plus_hover.png b/assets/voxygen/element/ui/settings/buttons/settings_button_plus_hover.png new file mode 100644 index 0000000000..35a6d8429c --- /dev/null +++ b/assets/voxygen/element/ui/settings/buttons/settings_button_plus_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f3571dbbed7107568ec8cab8db0ce3ed57c4cd88443bda794fdf7b3e0af3149 +size 602 diff --git a/assets/voxygen/element/ui/settings/buttons/settings_button_plus_press.png b/assets/voxygen/element/ui/settings/buttons/settings_button_plus_press.png new file mode 100644 index 0000000000..7651a301e0 --- /dev/null +++ b/assets/voxygen/element/ui/settings/buttons/settings_button_plus_press.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ce75822c53688a9ea25a699e1352275b298cf12ec90daaab0e91059712dac4e +size 602 diff --git a/assets/voxygen/element/ui/settings/chat_tab_settings_bg.png b/assets/voxygen/element/ui/settings/chat_tab_settings_bg.png new file mode 100644 index 0000000000..33129b6e9a --- /dev/null +++ b/assets/voxygen/element/ui/settings/chat_tab_settings_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afdf31e8bbe04c19315d7ac72d123aa453285064b0b43771d6491db9f1fbe3a3 +size 1266 diff --git a/assets/voxygen/element/ui/settings/chat_tab_settings_frame.png b/assets/voxygen/element/ui/settings/chat_tab_settings_frame.png new file mode 100644 index 0000000000..23937ed774 --- /dev/null +++ b/assets/voxygen/element/ui/settings/chat_tab_settings_frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03c6b635af22b4c26cdaa2d4cfab18830386875cd8be5100742697bfa696947 +size 1099 diff --git a/assets/voxygen/i18n/en/common.ron b/assets/voxygen/i18n/en/common.ron index 40c87b0c52..ef01237b8c 100644 --- a/assets/voxygen/i18n/en/common.ron +++ b/assets/voxygen/i18n/en/common.ron @@ -16,6 +16,7 @@ "common.controls": "Controls", "common.video": "Graphics", "common.sound": "Sound", + "common.chat": "Chat", "common.resume": "Resume", "common.characters": "Characters", "common.close": "Close", @@ -44,6 +45,7 @@ "common.video_settings": "Graphics Settings", "common.sound_settings": "Sound Settings", "common.language_settings": "Language Settings", + "common.chat_settings": "Chat Settings", // Message when connection to the server is lost "common.connection_lost": r#"Connection lost! diff --git a/assets/voxygen/i18n/en/hud/chat.ron b/assets/voxygen/i18n/en/hud/chat.ron index 394db4c2dc..443a0c4370 100644 --- a/assets/voxygen/i18n/en/hud/chat.ron +++ b/assets/voxygen/i18n/en/hud/chat.ron @@ -3,6 +3,9 @@ /// Localization for "global" English ( string_map: { + "hud.chat.all": "All", + "hud.chat.chat_tab_hover_tooltip": "Right click for settings", + // Debuff outcomes "hud.outcome.burning": "died of: burning", "hud.outcome.curse": "died of: curse", diff --git a/assets/voxygen/i18n/en/hud/hud_settings.ron b/assets/voxygen/i18n/en/hud/hud_settings.ron index 731724c352..037e4868d1 100644 --- a/assets/voxygen/i18n/en/hud/hud_settings.ron +++ b/assets/voxygen/i18n/en/hud/hud_settings.ron @@ -107,6 +107,23 @@ "hud.settings.awaitingkey": "Press a key...", "hud.settings.unbound": "None", "hud.settings.reset_keybinds": "Reset to Defaults", + + "hud.settings.chat_tabs": "Chat Tabs", + "hud.settings.label": "Label:", + "hud.settings.delete": "Delete", + "hud.settings.show_all": "Show all", + "hud.settings.messages": "Messages", + "hud.settings.activity": "Activity", + "hud.settings.death": "Death", + "hud.settings.group": "Group", + "hud.settings.faction": "Faction", + "hud.settings.world": "World", + "hud.settings.region": "Region", + "hud.settings.say": "Say", + "hud.settings.none": "None", + "hud.settings.all": "All", + "hud.settings.group_only": "Group only", + "hud.settings.reset_chat" : "Reset to Defaults", }, diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 188590b656..30d0c2cad1 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -1,15 +1,18 @@ use super::{ - img_ids::Imgs, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR, LOOT_COLOR, - OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, WORLD_COLOR, + img_ids::Imgs, ChatTab, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR, + LOOT_COLOR, OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, + WORLD_COLOR, }; -use crate::{i18n::Localization, ui::fonts::Fonts, GlobalState}; +use crate::{i18n::Localization, settings::chat::MAX_CHAT_TABS, ui::fonts::Fonts, GlobalState}; use client::{cmd, Client}; use common::comp::{ chat::{KillSource, KillType}, + group::Role, BuffKind, ChatMode, ChatMsg, ChatType, }; use common_net::msg::validate_chat_msg; use conrod_core::{ + color, input::Key, position::Dimension, text::{ @@ -17,9 +20,10 @@ use conrod_core::{ cursor::{self, Index}, }, widget::{self, Button, Id, Image, List, Rectangle, Text, TextEdit}, - widget_ids, Color, Colorable, Positionable, Sizeable, Ui, UiCell, Widget, WidgetCommon, + widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Ui, UiCell, Widget, + WidgetCommon, }; -use std::collections::VecDeque; +use std::collections::{HashSet, VecDeque}; widget_ids! { struct Ids { @@ -29,7 +33,15 @@ widget_ids! { chat_input_bg, chat_input_icon, chat_arrow, + chat_icon_align, chat_icons[], + + chat_tab_align, + chat_tab_all, + chat_tab_selected, + chat_tabs[], + chat_tab_tooltip_bg, + chat_tab_tooltip_text, } } /*#[const_tweaker::tweak(min = 0.0, max = 60.0, step = 1.0)] @@ -41,10 +53,14 @@ const CHAT_ICON_WIDTH: f64 = 16.0; const CHAT_ICON_HEIGHT: f64 = 16.0; const CHAT_BOX_WIDTH: f64 = 470.0; const CHAT_BOX_INPUT_WIDTH: f64 = 460.0 - CHAT_ICON_WIDTH - 1.0; -const CHAT_BOX_HEIGHT: f64 = 174.0; +const CHAT_BOX_HEIGHT: f64 = 154.0; + +const CHAT_TAB_HEIGHT: f64 = 20.0; +const CHAT_TAB_ALL_WIDTH: f64 = 40.0; #[derive(WidgetCommon)] pub struct Chat<'a> { + pulse: f32, new_messages: &'a mut VecDeque, client: &'a Client, force_input: Option, @@ -69,11 +85,13 @@ impl<'a> Chat<'a> { new_messages: &'a mut VecDeque, client: &'a Client, global_state: &'a GlobalState, + pulse: f32, imgs: &'a Imgs, fonts: &'a Fonts, localized_strings: &'a Localization, ) -> Self { Self { + pulse, new_messages, client, force_input: None, @@ -142,16 +160,22 @@ pub struct State { completions_index: Option, // At which character is tab completion happening completion_cursor: Option, + // last time mouse has been hovered + tabs_last_hover_pulse: Option, + // last chat_tab (used to see if chat tab has been changed) + prev_chat_tab: Option, } pub enum Event { TabCompletionStart(String), SendMessage(String), Focus(Id), + ChangeChatTab(Option), + ShowChatTabSettings(usize), } impl<'a> Widget for Chat<'a> { - type Event = Option; + type Event = Vec; type State = State; type Style = (); @@ -168,6 +192,8 @@ impl<'a> Widget for Chat<'a> { completions_index: None, completion_cursor: None, ids: Ids::new(id_gen), + tabs_last_hover_pulse: None, + prev_chat_tab: None, } } @@ -178,12 +204,23 @@ impl<'a> Widget for Chat<'a> { #[allow(clippy::single_match)] // TODO: Pending review in #587 fn update(self, args: widget::UpdateArgs) -> Self::Event { let widget::UpdateArgs { id, state, ui, .. } = args; - let transp = self.global_state.settings.interface.chat_transp; + + let mut events = Vec::new(); + + let chat_settings = &self.global_state.settings.chat; + + let chat_tabs = &chat_settings.chat_tabs; + let current_chat_tab = chat_settings.chat_tab_index.and_then(|i| chat_tabs.get(i)); + // Maintain scrolling. if !self.new_messages.is_empty() { state.update(|s| s.messages.extend(self.new_messages.drain(..))); ui.scroll_widget(state.ids.message_box, [0.0, std::f64::MAX]); } + if current_chat_tab != state.prev_chat_tab.as_ref() { + state.update(|s| s.prev_chat_tab = current_chat_tab.cloned()); + ui.scroll_widget(state.ids.message_box, [0.0, std::f64::MAX]); + } // Empty old messages state.update(|s| { @@ -322,7 +359,7 @@ impl<'a> Widget for Chat<'a> { _ => 0.0, }; Rectangle::fill([CHAT_BOX_WIDTH, y]) - .rgba(0.0, 0.0, 0.0, transp + 0.1) + .rgba(0.0, 0.0, 0.0, chat_settings.chat_transp + 0.1) .bottom_left_with_margins_on(ui.window, 10.0, 10.0) .w(CHAT_BOX_WIDTH) .set(state.ids.chat_input_bg, ui); @@ -341,7 +378,7 @@ impl<'a> Widget for Chat<'a> { // Message box Rectangle::fill([CHAT_BOX_WIDTH, CHAT_BOX_HEIGHT]) - .rgba(0.0, 0.0, 0.0, transp) + .rgba(0.0, 0.0, 0.0, chat_settings.chat_transp) .and(|r| { if input_focused { r.up_from(state.ids.chat_input_bg, 0.0) @@ -351,11 +388,6 @@ impl<'a> Widget for Chat<'a> { }) .crop_kids() .set(state.ids.message_box_bg, ui); - let (mut items, _) = List::flow_down(state.messages.len() + 1) - .top_left_with_margins_on(state.ids.message_box_bg, 0.0, 16.0) - .w_h(CHAT_BOX_WIDTH - 16.0, CHAT_BOX_HEIGHT) - .scroll_kids_vertically() - .set(state.ids.message_box, ui); if state.ids.chat_icons.len() < state.messages.len() { state.update(|s| { s.ids @@ -363,105 +395,70 @@ impl<'a> Widget for Chat<'a> { .resize(s.messages.len(), &mut ui.widget_id_generator()) }); } + let group_members = self + .client + .group_members() + .iter() + .filter_map(|(u, r)| match r { + Role::Member => Some(u), + Role::Pet => None, + }) + .collect::>(); + let show_char_name = chat_settings.chat_character_name; + let messages = &state + .messages + .iter() + .map(|m| { + let mut message = m.clone(); + if let Some(template_key) = get_chat_template_key(&message.chat_type) { + message.message = self.localized_strings.get(template_key).to_string(); + if let ChatType::Kill(kill_source, _) = &message.chat_type { + match kill_source { + KillSource::Player(_, KillType::Buff(buffkind)) + | KillSource::NonExistent(KillType::Buff(buffkind)) + | KillSource::NonPlayer(_, KillType::Buff(buffkind)) => { + message.message = insert_killing_buff( + *buffkind, + self.localized_strings, + &message.message, + ); + }, + _ => {}, + } + } + } + message.message = self.client.format_message(&message, show_char_name); + message + }) + .filter(|m| { + if let Some(chat_tab) = current_chat_tab { + chat_tab.filter.satisfies(&m, &group_members) + } else { + true + } + }) + .collect::>(); + Rectangle::fill_with([CHAT_ICON_WIDTH, CHAT_BOX_HEIGHT], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.message_box_bg, 0.0, 0.0) + .crop_kids() + .set(state.ids.chat_icon_align, ui); + let (mut items, _) = List::flow_down(messages.len() + 1) + .top_left_with_margins_on(state.ids.message_box_bg, 0.0, CHAT_ICON_WIDTH) + .w_h(CHAT_BOX_WIDTH - CHAT_ICON_WIDTH, CHAT_BOX_HEIGHT) + .scroll_kids_vertically() + .set(state.ids.message_box, ui); - let show_char_name = self.global_state.settings.interface.chat_character_name; while let Some(item) = items.next(ui) { // This would be easier if conrod used the v-metrics from rusttype. - if item.i < state.messages.len() { - let mut message = state.messages[item.i].clone(); + if item.i < messages.len() { + let message = &messages[item.i]; let (color, icon) = render_chat_line(&message.chat_type, &self.imgs); - let ChatMsg { chat_type, .. } = &message; // For each ChatType needing localization get/set matching pre-formatted // localized string. This string will be formatted with the data // provided in ChatType in the client/src/mod.rs // fn format_message called below - message.message = match chat_type { - ChatType::Online(_) => self - .localized_strings - .get("hud.chat.online_msg") - .to_string(), - ChatType::Offline(_) => self - .localized_strings - .get("hud.chat.offline_msg") - .to_string(), - ChatType::Kill(kill_source, _) => match kill_source { - KillSource::Player(_, KillType::Buff(buffkind)) => insert_killing_buff( - *buffkind, - self.localized_strings, - self.localized_strings.get("hud.chat.died_of_pvp_buff_msg"), - ), - KillSource::Player(_, KillType::Melee) => self - .localized_strings - .get("hud.chat.pvp_melee_kill_msg") - .to_string(), - KillSource::Player(_, KillType::Projectile) => self - .localized_strings - .get("hud.chat.pvp_ranged_kill_msg") - .to_string(), - KillSource::Player(_, KillType::Explosion) => self - .localized_strings - .get("hud.chat.pvp_explosion_kill_msg") - .to_string(), - KillSource::Player(_, KillType::Energy) => self - .localized_strings - .get("hud.chat.pvp_energy_kill_msg") - .to_string(), - KillSource::Player(_, KillType::Other) => self - .localized_strings - .get("hud.chat.pvp_other_kill_msg") - .to_string(), - KillSource::NonExistent(KillType::Buff(buffkind)) => insert_killing_buff( - *buffkind, - self.localized_strings, - self.localized_strings - .get("hud.chat.died_of_buff_nonexistent_msg"), - ), - KillSource::NonPlayer(_, KillType::Buff(buffkind)) => insert_killing_buff( - *buffkind, - self.localized_strings, - self.localized_strings.get("hud.chat.died_of_npc_buff_msg"), - ), - KillSource::NonPlayer(_, KillType::Melee) => self - .localized_strings - .get("hud.chat.npc_melee_kill_msg") - .to_string(), - KillSource::NonPlayer(_, KillType::Projectile) => self - .localized_strings - .get("hud.chat.npc_ranged_kill_msg") - .to_string(), - KillSource::NonPlayer(_, KillType::Explosion) => self - .localized_strings - .get("hud.chat.npc_explosion_kill_msg") - .to_string(), - KillSource::NonPlayer(_, KillType::Energy) => self - .localized_strings - .get("hud.chat.npc_energy_kill_msg") - .to_string(), - KillSource::NonPlayer(_, KillType::Other) => self - .localized_strings - .get("hud.chat.npc_other_kill_msg") - .to_string(), - KillSource::Environment(_) => self - .localized_strings - .get("hud.chat.environmental_kill_msg") - .to_string(), - KillSource::FallDamage => self - .localized_strings - .get("hud.chat.fall_kill_msg") - .to_string(), - KillSource::Suicide => self - .localized_strings - .get("hud.chat.suicide_msg") - .to_string(), - KillSource::NonExistent(_) | KillSource::Other => self - .localized_strings - .get("hud.chat.default_death_msg") - .to_string(), - }, - _ => message.message, - }; - let msg = self.client.format_message(&message, show_char_name); - let text = Text::new(&msg) + + let text = Text::new(&message.message) .font_size(self.fonts.opensans.scale(15)) .font_id(self.fonts.opensans.conrod_id) .w(CHAT_BOX_WIDTH - 17.0) @@ -477,7 +474,7 @@ impl<'a> Widget for Chat<'a> { Image::new(icon) .w_h(CHAT_ICON_WIDTH, CHAT_ICON_HEIGHT) .top_left_with_margins_on(item.widget_id, 2.0, -CHAT_ICON_WIDTH) - .parent(state.ids.message_box_bg) + .parent(state.ids.chat_icon_align) .set(icon_id, ui); } else { // Spacer at bottom of the last message so that it is not cut off. @@ -492,6 +489,125 @@ impl<'a> Widget for Chat<'a> { }; } + //Chat tabs + if ui + .rect_of(state.ids.message_box_bg) + .map_or(false, |r| r.is_over(ui.global_input().current.mouse.xy)) + { + state.update(|s| s.tabs_last_hover_pulse = Some(self.pulse)); + } + + if let Some(time_since_hover) = state + .tabs_last_hover_pulse + .map(|t| self.pulse - t) + .filter(|t| t <= &1.5) + { + let alpha = 1.0 - (time_since_hover / 1.5).powi(4); + let shading = color::rgba(1.0, 1.0, 1.0, (chat_settings.chat_transp + 0.1) * alpha); + + Rectangle::fill([CHAT_BOX_WIDTH, CHAT_TAB_HEIGHT]) + .rgba(0.0, 0.0, 0.0, (chat_settings.chat_transp + 0.1) * alpha) + .up_from(state.ids.message_box_bg, 0.0) + .set(state.ids.chat_tab_align, ui); + if ui + .rect_of(state.ids.chat_tab_align) + .map_or(false, |r| r.is_over(ui.global_input().current.mouse.xy)) + { + state.update(|s| s.tabs_last_hover_pulse = Some(self.pulse)); + } + + if Button::image(if chat_settings.chat_tab_index.is_none() { + self.imgs.selection + } else { + self.imgs.nothing + }) + .top_left_with_margins_on(state.ids.chat_tab_align, 0.0, 0.0) + .w_h(CHAT_TAB_ALL_WIDTH, CHAT_TAB_HEIGHT) + .hover_image(self.imgs.selection_hover) + .hover_image(self.imgs.selection_press) + .image_color(shading) + .label(&self.localized_strings.get("hud.chat.all")) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_color(TEXT_COLOR.alpha(alpha)) + .set(state.ids.chat_tab_all, ui) + .was_clicked() + { + events.push(Event::ChangeChatTab(None)); + } + + let chat_tab_width = (CHAT_BOX_WIDTH - CHAT_TAB_ALL_WIDTH) / (MAX_CHAT_TABS as f64); + + if state.ids.chat_tabs.len() < chat_tabs.len() { + state.update(|s| { + s.ids + .chat_tabs + .resize(chat_tabs.len(), &mut ui.widget_id_generator()) + }); + } + for (i, chat_tab) in chat_tabs.iter().enumerate() { + if Button::image(if chat_settings.chat_tab_index == Some(i) { + self.imgs.selection + } else { + self.imgs.nothing + }) + .w_h(chat_tab_width, CHAT_TAB_HEIGHT) + .hover_image(self.imgs.selection_hover) + .press_image(self.imgs.selection_press) + .image_color(shading) + .label(chat_tab.label.as_str()) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_color(TEXT_COLOR.alpha(alpha)) + .right_from( + if i == 0 { + state.ids.chat_tab_all + } else { + state.ids.chat_tabs[i - 1] + }, + 0.0, + ) + .set(state.ids.chat_tabs[i], ui) + .was_clicked() + { + events.push(Event::ChangeChatTab(Some(i))); + } + + if ui + .widget_input(state.ids.chat_tabs[i]) + .mouse() + .map_or(false, |m| m.is_over()) + { + Rectangle::fill([120.0, 20.0]) + .rgba(0.0, 0.0, 0.0, 0.9) + .top_left_with_margins_on(state.ids.chat_tabs[i], -20.0, 5.0) + .parent(id) + .set(state.ids.chat_tab_tooltip_bg, ui); + + Text::new( + &self + .localized_strings + .get("hud.chat.chat_tab_hover_tooltip"), + ) + .mid_top_with_margin_on(state.ids.chat_tab_tooltip_bg, 3.0) + .font_size(self.fonts.cyri.scale(10)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.chat_tab_tooltip_text, ui); + } + + if ui + .widget_input(state.ids.chat_tabs[i]) + .clicks() + .right() + .next() + .is_some() + { + events.push(Event::ShowChatTabSettings(i)); + } + } + } + // Chat Arrow // Check if already at bottom. if !Self::scrolled_to_bottom(state, ui) @@ -509,11 +625,11 @@ impl<'a> Widget for Chat<'a> { // We've started a new tab completion. Populate tab completion suggestions. if request_tab_completions { - Some(Event::TabCompletionStart(state.input.message.to_string())) + events.push(Event::TabCompletionStart(state.input.message.to_string())); // If the chat widget is focused, return a focus event to pass the focus // to the input box. } else if keyboard_capturer == Some(id) { - Some(Event::Focus(state.ids.chat_input)) + events.push(Event::Focus(state.ids.chat_input)); } // If enter is pressed and the input box is not empty, send the current message. else if ui.widget_input(state.ids.chat_input).presses().key().any( @@ -530,10 +646,9 @@ impl<'a> Widget for Chat<'a> { s.history.truncate(self.history_max); } }); - Some(Event::SendMessage(msg)) - } else { - None + events.push(Event::SendMessage(msg)); } + events } } @@ -646,3 +761,30 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat template.replace("{died_of_buff}", buff_outcome) } + +fn get_chat_template_key(chat_type: &ChatType) -> Option<&str> { + Some(match chat_type { + ChatType::Online(_) => "hud.chat.online_msg", + ChatType::Offline(_) => "hud.chat.offline_msg", + ChatType::Kill(kill_source, _) => match kill_source { + KillSource::Player(_, KillType::Buff(_)) => "hud.chat.died_of_pvp_buff_msg", + KillSource::Player(_, KillType::Melee) => "hud.chat.pvp_melee_kill_msg", + KillSource::Player(_, KillType::Projectile) => "hud.chat.pvp_ranged_kill_msg", + KillSource::Player(_, KillType::Explosion) => "hud.chat.pvp_explosion_kill_msg", + KillSource::Player(_, KillType::Energy) => "hud.chat.pvp_energy_kill_msg", + KillSource::Player(_, KillType::Other) => "hud.chat.pvp_other_kill_msg", + KillSource::NonExistent(KillType::Buff(_)) => "hud.chat.died_of_buff_nonexistent_msg", + KillSource::NonPlayer(_, KillType::Buff(_)) => "hud.chat.died_of_npc_buff_msg", + KillSource::NonPlayer(_, KillType::Melee) => "hud.chat.npc_melee_kill_msg", + KillSource::NonPlayer(_, KillType::Projectile) => "hud.chat.npc_ranged_kill_msg", + KillSource::NonPlayer(_, KillType::Explosion) => "hud.chat.npc_explosion_kill_msg", + KillSource::NonPlayer(_, KillType::Energy) => "hud.chat.npc_energy_kill_msg", + KillSource::NonPlayer(_, KillType::Other) => "hud.chat.npc_other_kill_msg", + KillSource::Environment(_) => "hud.chat.environmental_kill_msg", + KillSource::FallDamage => "hud.chat.fall_kill_msg", + KillSource::Suicide => "hud.chat.suicide_msg", + KillSource::NonExistent(_) | KillSource::Other => "hud.chat.default_death_msg", + }, + _ => return None, + }) +} diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 2e670e7517..ae25ebf567 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -122,6 +122,13 @@ image_ids! { settings_button_hover: "voxygen.element.ui.settings.buttons.settings_button_hover", settings_button_press: "voxygen.element.ui.settings.buttons.settings_button_press", + settings_plus: "voxygen.element.ui.settings.buttons.settings_button_plus", + settings_plus_hover: "voxygen.element.ui.settings.buttons.settings_button_plus_hover", + settings_plus_press: "voxygen.element.ui.settings.buttons.settings_button_plus_press", + + chat_tab_settings_bg: "voxygen.element.ui.settings.chat_tab_settings_bg", + chat_tab_settings_frame: "voxygen.element.ui.settings.chat_tab_settings_frame", + quest_bg: "voxygen.element.ui.quests.temp_quest_bg", // Slider diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index c4c8cf2a7d..de3a920bc7 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -55,9 +55,10 @@ use crate::{ render::{Consts, Globals, Renderer}, scene::camera::{self, Camera}, session::{ - settings_change::{Interface as InterfaceChange, SettingsChange}, + settings_change::{Chat as ChatChange, Interface as InterfaceChange, SettingsChange}, Interactable, }, + settings::chat::ChatFilter, ui::{ fonts::Fonts, img_ids::Rotations, slot, slot::SlotKey, Graphic, Ingameable, ScaleMode, Ui, }, @@ -466,6 +467,19 @@ pub enum PressBehavior { #[serde(other)] Toggle = 0, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ChatTab { + pub label: String, + pub filter: ChatFilter, +} +impl Default for ChatTab { + fn default() -> Self { + Self { + label: String::from("Chat"), + filter: ChatFilter::default(), + } + } +} impl PressBehavior { pub fn update(&self, keystate: bool, setting: &mut bool, f: impl FnOnce(bool)) { @@ -503,6 +517,7 @@ pub struct Show { open_windows: Windows, map: bool, ingame: bool, + chat_tab_settings_index: Option, settings_tab: SettingsTab, skilltreetab: SelectedSkillTree, crafting_tab: CraftingTab, @@ -869,6 +884,7 @@ impl Hud { diary: false, group: false, group_menu: false, + chat_tab_settings_index: None, settings_tab: SettingsTab::Interface, skilltreetab: SelectedSkillTree::General, crafting_tab: CraftingTab::All, @@ -2585,10 +2601,11 @@ impl Hud { .retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_, _))); // Chat box - match Chat::new( + for event in Chat::new( &mut self.new_messages, &client, global_state, + self.pulse, &self.imgs, &self.fonts, i18n, @@ -2600,16 +2617,25 @@ impl Hud { .and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos)) .set(self.ids.chat, ui_widgets) { - Some(chat::Event::TabCompletionStart(input)) => { - self.tab_complete = Some(input); - }, - Some(chat::Event::SendMessage(message)) => { - events.push(Event::SendMessage(message)); - }, - Some(chat::Event::Focus(focus_id)) => { - self.to_focus = Some(Some(focus_id)); - }, - None => {}, + match event { + chat::Event::TabCompletionStart(input) => { + self.tab_complete = Some(input); + }, + chat::Event::SendMessage(message) => { + events.push(Event::SendMessage(message)); + }, + chat::Event::Focus(focus_id) => { + self.to_focus = Some(Some(focus_id)); + }, + chat::Event::ChangeChatTab(tab) => { + events.push(Event::SettingsChange(ChatChange::ChangeChatTab(tab).into())); + }, + chat::Event::ShowChatTabSettings(tab) => { + self.show.chat_tab_settings_index = Some(tab); + self.show.settings_tab = SettingsTab::Chat; + self.show.settings(true); + }, + } } self.new_messages = VecDeque::new(); @@ -2644,6 +2670,9 @@ impl Hud { self.show.settings(false) }, + settings_window::Event::ChangeChatSettingsTab(tab) => { + self.show.chat_tab_settings_index = tab; + }, settings_window::Event::SettingsChange(settings_change) => { match &settings_change { SettingsChange::Interface(interface_change) => match interface_change { diff --git a/voxygen/src/hud/settings_window/chat.rs b/voxygen/src/hud/settings_window/chat.rs new file mode 100644 index 0000000000..12b5f9b22f --- /dev/null +++ b/voxygen/src/hud/settings_window/chat.rs @@ -0,0 +1,658 @@ +use super::{RESET_BUTTONS_HEIGHT, RESET_BUTTONS_WIDTH}; + +use crate::{ + hud::{img_ids::Imgs, ChatTab, Show, TEXT_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN}, + i18n::Localization, + session::settings_change::{Chat as ChatChange, Chat::*}, + settings::chat::MAX_CHAT_TABS, + ui::{fonts::Fonts, ImageSlider, ToggleButton}, + GlobalState, +}; +use conrod_core::{ + color, + position::Relative, + widget::{self, Button, DropDownList, Image, Rectangle, Text, TextEdit}, + widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, +}; +use std::cmp::Ordering; + +widget_ids! { + struct Ids { + window, + window_r, + general_txt, + transp_text, + transp_slider, + char_name_text, + char_name_button, + reset_chat_button, + + //Tabs + tabs_frame, + tabs_bg, + tabs_text, + tab_align, + tab_add, + tabs[], + + //tab content + tab_content_align, + tab_content_align_r, + tab_label_text, + tab_label_input, + tab_label_bg, + btn_tab_delete, + + text_messages, + btn_messages_all, + text_messages_all, + btn_messages_world, + text_messages_world, + icon_messages_world, + btn_messages_region, + text_messages_region, + icon_messages_region, + btn_messages_faction, + text_messages_faction, + icon_messages_faction, + btn_messages_group, + text_messages_group, + icon_messages_group, + btn_messages_say, + text_messages_say, + icon_messages_say, + + text_activity, + list_activity, + + text_death, + list_death, + } +} + +#[derive(WidgetCommon)] +pub struct Chat<'a> { + global_state: &'a GlobalState, + show: &'a Show, + imgs: &'a Imgs, + fonts: &'a Fonts, + localized_strings: &'a Localization, + #[conrod(common_builder)] + common: widget::CommonBuilder, +} +impl<'a> Chat<'a> { + pub fn new( + global_state: &'a GlobalState, + show: &'a Show, + imgs: &'a Imgs, + fonts: &'a Fonts, + localized_strings: &'a Localization, + ) -> Self { + Self { + global_state, + show, + imgs, + fonts, + localized_strings, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} +pub enum Event { + ChangeChatSettingsTab(Option), + ChatChange(ChatChange), +} + +impl<'a> Widget for Chat<'a> { + type Event = Vec; + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + #[allow(clippy::unused_unit)] // TODO: Pending review in #587 + fn style(&self) -> Self::Style { () } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + let mut events = Vec::new(); + let chat_settings = &self.global_state.settings.chat; + // Alignment + // Settings Window + Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT) + .xy(args.rect.xy()) + .graphics_for(args.id) + .scroll_kids() + .scroll_kids_vertically() + .set(state.ids.window, ui); + // Right Side + Rectangle::fill_with([args.rect.w() / 2.0, args.rect.h()], color::TRANSPARENT) + .top_right_of(state.ids.window) + .set(state.ids.window_r, ui); + + // General Title + Text::new(&self.localized_strings.get("hud.settings.general")) + .top_left_with_margins_on(state.ids.window, 5.0, 5.0) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.general_txt, ui); + + // Chat Transp + Text::new( + &self + .localized_strings + .get("hud.settings.background_transparency"), + ) + .down_from(state.ids.general_txt, 20.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.transp_text, ui); + if let Some(new_val) = ImageSlider::continuous( + chat_settings.chat_transp, + 0.0, + 0.9, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(104.0, 22.0) + .down_from(state.ids.transp_text, 10.0) + .track_breadth(12.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.transp_slider, ui) + { + events.push(Event::ChatChange(Transp(new_val))); + } + + // "Show character names in chat" toggle button + Text::new( + &self + .localized_strings + .get("hud.settings.chat_character_name"), + ) + .down_from(state.ids.transp_slider, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.char_name_text, ui); + + if chat_settings.chat_character_name + != ToggleButton::new( + chat_settings.chat_character_name, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.char_name_text, 10.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.char_name_button, ui) + { + events.push(Event::ChatChange(CharName( + !chat_settings.chat_character_name, + ))); + } + + // Reset the chat settings to the default settings + if Button::image(self.imgs.button) + .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .down_from(state.ids.char_name_text, 20.0) + .label(&self.localized_strings.get("hud.settings.reset_chat")) + .label_font_size(self.fonts.cyri.scale(14)) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .label_y(Relative::Scalar(2.0)) + .set(state.ids.reset_chat_button, ui) + .was_clicked() + { + events.push(Event::ChatChange(ResetChatSettings)); + } + + // Tabs Title + Text::new(&self.localized_strings.get("hud.settings.chat_tabs")) + .top_left_with_margins_on(state.ids.window_r, 5.0, 5.0) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.tabs_text, ui); + + // bg and frame + Image::new(self.imgs.chat_tab_settings_bg) + .w_h(390.0, 270.0) + .color(Some(UI_MAIN)) + .down_from(state.ids.tabs_text, 20.0) + .set(state.ids.tabs_bg, ui); + + Image::new(self.imgs.chat_tab_settings_frame) + .w_h(390.0, 270.0) + .color(Some(UI_HIGHLIGHT_0)) + .down_from(state.ids.tabs_text, 20.0) + .set(state.ids.tabs_frame, ui); + + // Tabs Alignment + Rectangle::fill_with([390.0, 20.0], color::TRANSPARENT) + .down_from(state.ids.tabs_text, 20.0) + .set(state.ids.tab_align, ui); + + // Tabs Settings Alignment + Rectangle::fill_with([390.0, 250.0], color::TRANSPARENT) + .down_from(state.ids.tab_align, 0.0) + .set(state.ids.tab_content_align, ui); + Rectangle::fill_with([195.0, 250.0], color::TRANSPARENT) + .top_right_of(state.ids.tab_content_align) + .set(state.ids.tab_content_align_r, ui); + + let chat_tabs = &chat_settings.chat_tabs; + if state.ids.tabs.len() < chat_tabs.len() { + state.update(|s| { + s.ids + .tabs + .resize(chat_tabs.len(), &mut ui.widget_id_generator()) + }); + } + for (i, chat_tab) in chat_tabs.iter().enumerate() { + let is_selected = self + .show + .chat_tab_settings_index + .map(|index| index == i) + .unwrap_or(false); + + let button = Button::image(if is_selected { + self.imgs.selection + } else { + self.imgs.nothing + }) + .w_h(390.0 / (MAX_CHAT_TABS as f64), 19.0) + .hover_image(self.imgs.selection_hover) + .press_image(self.imgs.selection_press) + .label(chat_tab.label.as_str()) + .label_font_size(self.fonts.cyri.scale(12)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_color(TEXT_COLOR) + .label_y(Relative::Scalar(1.0)); + + let button = if i == 0 { + button.top_left_with_margins_on(state.ids.tab_align, 1.0, 1.0) + } else { + button.right_from(state.ids.tabs[i - 1], 0.0) + }; + if button.set(state.ids.tabs[i], ui).was_clicked() { + events.push(Event::ChangeChatSettingsTab(if is_selected { + None + } else { + Some(i) + })); + } + } + //Add button + if chat_tabs.len() < MAX_CHAT_TABS { + let add_tab_button = Button::image(self.imgs.settings_plus) + .hover_image(self.imgs.settings_plus_hover) + .press_image(self.imgs.settings_plus_press) + .w_h(19.0, 19.0); + + let add_tab_button = if chat_tabs.is_empty() { + add_tab_button.top_left_with_margins_on(state.ids.tab_align, 1.0, 1.0) + } else { + add_tab_button.right_from(state.ids.tabs[chat_tabs.len() - 1], 0.0) + }; + + if add_tab_button.set(state.ids.tab_add, ui).was_clicked() { + let index = chat_tabs.len(); + events.push(Event::ChatChange(ChatTabInsert(index, ChatTab::default()))); + events.push(Event::ChangeChatSettingsTab(Some(index))); + } + } + + //Content + if let Some((index, chat_tab)) = self + .show + .chat_tab_settings_index + .and_then(|i| chat_tabs.get(i).map(|ct| (i, ct))) + { + let mut updated_chat_tab = chat_tab.clone(); + + Text::new(&self.localized_strings.get("hud.settings.label")) + .top_left_with_margins_on(state.ids.tab_content_align, 5.0, 25.0) + .font_size(self.fonts.cyri.scale(16)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.tab_label_text, ui); + + Rectangle::fill([90.0, 20.0]) + .right_from(state.ids.tab_label_text, 5.0) + .color(color::rgba(0.0, 0.0, 0.0, 0.7)) + .set(state.ids.tab_label_bg, ui); + + if let Some(label) = TextEdit::new(chat_tab.label.as_str()) + .right_from(state.ids.tab_label_text, 10.0) + .y_relative_to(state.ids.tab_label_text, -3.0) + .w_h(75.0, 20.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(TEXT_COLOR) + .set(state.ids.tab_label_input, ui) + { + updated_chat_tab.label = label; + } + + if Button::image(self.imgs.button) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .w_h(100.0, 30.0) + .label(&self.localized_strings.get("hud.settings.delete")) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_color(TEXT_COLOR) + .label_y(Relative::Scalar(1.0)) + .bottom_right_with_margins_on(state.ids.tab_content_align, 10.0, 10.0) + .set(state.ids.btn_tab_delete, ui) + .was_clicked() + { + events.push(Event::ChatChange(ChatTabRemove(index))); + events.push(Event::ChangeChatSettingsTab(None)); + + if let Some(chat_tab_index) = chat_settings.chat_tab_index { + match chat_tab_index.cmp(&index) { + Ordering::Equal => { + events.push(Event::ChatChange(ChangeChatTab(None))); + }, + Ordering::Greater => { + events.push(Event::ChatChange(ChangeChatTab(Some(index - 1)))); + }, + _ => {}, + } + } + } + + //helper methods to reduce on repeated code + //(TODO: perhaps introduce a checkbox with label widget) + let create_toggle = |selected, enabled| { + ToggleButton::new(selected, self.imgs.checkbox, self.imgs.checkbox_checked) + .and(|button| { + if enabled { + button + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + } else { + button.image_colors(TEXT_GRAY_COLOR, TEXT_GRAY_COLOR) + } + }) + .w_h(16.0, 16.0) + }; + + let create_toggle_text = |text, enabled| { + Text::new(text) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(if enabled { TEXT_COLOR } else { TEXT_GRAY_COLOR }) + }; + + let create_toggle_icon = |img, enabled: bool| { + Image::new(img) + .and_if(!enabled, |image| image.color(Some(TEXT_GRAY_COLOR))) + .w_h(18.0, 18.0) + }; + + //Messages + Text::new(&self.localized_strings.get("hud.settings.messages")) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(16)) + .color(TEXT_COLOR) + .top_left_with_margins_on(state.ids.tab_content_align, 35.0, 15.0) + .set(state.ids.text_messages, ui); + + // Toggle all options + if chat_tab.filter.message_all + != ToggleButton::new( + chat_tab.filter.message_all, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .w_h(18.0, 18.0) + .down_from(state.ids.text_messages, 10.0) + .set(state.ids.btn_messages_all, ui) + { + updated_chat_tab.filter.message_all = !chat_tab.filter.message_all; + }; + + Text::new(&self.localized_strings.get("hud.settings.show_all")) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(16)) + .color(TEXT_COLOR) + .right_from(state.ids.btn_messages_all, 5.0) + .set(state.ids.text_messages_all, ui); + + //Messages - group + if chat_tab.filter.message_group + != create_toggle(chat_tab.filter.message_group, !chat_tab.filter.message_all) + .down_from(state.ids.btn_messages_all, 10.0) + .set(state.ids.btn_messages_group, ui) + && !chat_tab.filter.message_all + { + updated_chat_tab.filter.message_group = !chat_tab.filter.message_group; + } + + create_toggle_text( + &self.localized_strings.get("hud.settings.group"), + !chat_tab.filter.message_all, + ) + .right_from(state.ids.btn_messages_group, 5.0) + .set(state.ids.text_messages_group, ui); + + create_toggle_icon(self.imgs.chat_group_small, !chat_tab.filter.message_all) + .right_from(state.ids.text_messages_group, 5.0) + .set(state.ids.icon_messages_group, ui); + + //Messages - faction + if chat_tab.filter.message_faction + != create_toggle( + chat_tab.filter.message_faction, + !chat_tab.filter.message_all, + ) + .down_from(state.ids.btn_messages_group, 10.0) + .set(state.ids.btn_messages_faction, ui) + && !chat_tab.filter.message_all + { + updated_chat_tab.filter.message_faction = !chat_tab.filter.message_faction; + } + + create_toggle_text( + &self.localized_strings.get("hud.settings.faction"), + !chat_tab.filter.message_all, + ) + .right_from(state.ids.btn_messages_faction, 5.0) + .set(state.ids.text_messages_faction, ui); + + create_toggle_icon(self.imgs.chat_faction_small, !chat_tab.filter.message_all) + .right_from(state.ids.text_messages_faction, 5.0) + .set(state.ids.icon_messages_faction, ui); + + //Messages - world + if chat_tab.filter.message_world + != create_toggle(chat_tab.filter.message_world, !chat_tab.filter.message_all) + .down_from(state.ids.btn_messages_faction, 10.0) + .set(state.ids.btn_messages_world, ui) + && !chat_tab.filter.message_all + { + updated_chat_tab.filter.message_world = !chat_tab.filter.message_world; + } + + create_toggle_text( + &self.localized_strings.get("hud.settings.world"), + !chat_tab.filter.message_all, + ) + .right_from(state.ids.btn_messages_world, 5.0) + .set(state.ids.text_messages_world, ui); + + create_toggle_icon(self.imgs.chat_world_small, !chat_tab.filter.message_all) + .right_from(state.ids.text_messages_world, 5.0) + .set(state.ids.icon_messages_world, ui); + + //Messages - region + if chat_tab.filter.message_region + != create_toggle(chat_tab.filter.message_region, !chat_tab.filter.message_all) + .down_from(state.ids.btn_messages_world, 10.0) + .set(state.ids.btn_messages_region, ui) + && !chat_tab.filter.message_all + { + updated_chat_tab.filter.message_region = !chat_tab.filter.message_region; + } + + create_toggle_text( + &self.localized_strings.get("hud.settings.region"), + !chat_tab.filter.message_all, + ) + .right_from(state.ids.btn_messages_region, 5.0) + .set(state.ids.text_messages_region, ui); + + create_toggle_icon(self.imgs.chat_region_small, !chat_tab.filter.message_all) + .right_from(state.ids.text_messages_region, 5.0) + .set(state.ids.icon_messages_region, ui); + + //Messages - say + if chat_tab.filter.message_say + != create_toggle(chat_tab.filter.message_say, !chat_tab.filter.message_all) + .down_from(state.ids.btn_messages_region, 10.0) + .set(state.ids.btn_messages_say, ui) + && !chat_tab.filter.message_all + { + updated_chat_tab.filter.message_say = !chat_tab.filter.message_say; + } + + create_toggle_text( + &self.localized_strings.get("hud.settings.say"), + !chat_tab.filter.message_all, + ) + .right_from(state.ids.btn_messages_say, 5.0) + .set(state.ids.text_messages_say, ui); + + create_toggle_icon(self.imgs.chat_say_small, !chat_tab.filter.message_all) + .right_from(state.ids.text_messages_say, 5.0) + .set(state.ids.icon_messages_say, ui); + + //Activity + Text::new(&self.localized_strings.get("hud.settings.activity")) + .top_left_with_margins_on(state.ids.tab_content_align_r, 0.0, 5.0) + .align_middle_y_of(state.ids.text_messages) + .font_size(self.fonts.cyri.scale(16)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.text_activity, ui); + + if let Some(clicked) = DropDownList::new( + &[ + &self.localized_strings.get("hud.settings.none"), + &self.localized_strings.get("hud.settings.all"), + &self.localized_strings.get("hud.settings.group_only"), + ], + Some(if chat_tab.filter.activity_all { + //all + 1 + } else if chat_tab.filter.activity_group { + //group only + 2 + } else { + //none + 0 + }), + ) + .w_h(100.0, 20.0) + .color(color::hsl(0.0, 0.0, 0.1)) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(14)) + .label_y(Relative::Scalar(1.0)) + .down_from(state.ids.text_activity, 10.0) + .set(state.ids.list_activity, ui) + { + match clicked { + 0 => { + updated_chat_tab.filter.activity_all = false; + updated_chat_tab.filter.activity_group = false; + }, + 1 => { + updated_chat_tab.filter.activity_all = true; + }, + 2 => { + updated_chat_tab.filter.activity_all = false; + updated_chat_tab.filter.activity_group = true; + }, + _ => unreachable!(), + } + } + + //Death + Text::new(&self.localized_strings.get("hud.settings.death")) + .down_from(state.ids.list_activity, 20.0) + .font_size(self.fonts.cyri.scale(16)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.text_death, ui); + + if let Some(clicked) = DropDownList::new( + &[ + &self.localized_strings.get("hud.settings.none"), + &self.localized_strings.get("hud.settings.all"), + &self.localized_strings.get("hud.settings.group_only"), + ], + Some(if chat_tab.filter.death_all { + //all + 1 + } else if chat_tab.filter.death_group { + //group only + 2 + } else { + //none + 0 + }), + ) + .w_h(100.0, 20.0) + .color(color::hsl(0.0, 0.0, 0.1)) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(14)) + .label_y(Relative::Scalar(1.0)) + .down_from(state.ids.text_death, 10.0) + .set(state.ids.list_death, ui) + { + match clicked { + 0 => { + updated_chat_tab.filter.death_all = false; + updated_chat_tab.filter.death_group = false; + }, + 1 => { + updated_chat_tab.filter.death_all = true; + }, + 2 => { + updated_chat_tab.filter.death_all = false; + updated_chat_tab.filter.death_group = true; + }, + _ => unreachable!(), + } + } + + if chat_tab != &updated_chat_tab { + //insert to front to avoid errors where the tab is moved or removed + events.insert(0, Event::ChatChange(ChatTabUpdate(index, updated_chat_tab))); + } + } + + events + } +} diff --git a/voxygen/src/hud/settings_window/interface.rs b/voxygen/src/hud/settings_window/interface.rs index f2f3f69017..0d67613e96 100644 --- a/voxygen/src/hud/settings_window/interface.rs +++ b/voxygen/src/hud/settings_window/interface.rs @@ -66,12 +66,6 @@ widget_ids! { buff_pos_map_button, buff_pos_map_text, // - chat_transp_title, - chat_transp_text, - chat_transp_slider, - chat_char_name_text, - chat_char_name_button, - // sct_title, sct_show_text, sct_show_radio, @@ -164,7 +158,6 @@ impl<'a> Widget for Interface<'a> { let crosshair_transp = self.global_state.settings.interface.crosshair_transp; let crosshair_type = self.global_state.settings.interface.crosshair_type; let ui_scale = self.global_state.settings.interface.ui_scale; - let chat_transp = self.global_state.settings.interface.chat_transp; Text::new(&self.localized_strings.get("hud.settings.general")) .top_left_with_margins_on(state.ids.window, 5.0, 5.0) @@ -942,70 +935,6 @@ impl<'a> Widget for Interface<'a> { .color(TEXT_COLOR) .set(state.ids.show_bar_numbers_percentage_text, ui); - // Chat Transp - Text::new(&self.localized_strings.get("hud.settings.chat")) - .down_from(state.ids.show_bar_numbers_percentage_button, 20.0) - .font_size(self.fonts.cyri.scale(18)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.chat_transp_title, ui); - Text::new( - &self - .localized_strings - .get("hud.settings.background_transparency"), - ) - .right_from(state.ids.chat_transp_slider, 20.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.chat_transp_text, ui); - - if let Some(new_val) = ImageSlider::continuous( - chat_transp, - 0.0, - 0.9, - self.imgs.slider_indicator, - self.imgs.slider, - ) - .w_h(104.0, 22.0) - .down_from(state.ids.chat_transp_title, 8.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(state.ids.chat_transp_slider, ui) - { - events.push(ChatTransp(new_val)); - } - - // "Show character names in chat" toggle button - let chat_char_name = ToggleButton::new( - self.global_state.settings.interface.chat_character_name, - self.imgs.checkbox, - self.imgs.checkbox_checked, - ) - .w_h(18.0, 18.0) - .down_from(state.ids.chat_transp_slider, 20.0) - .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) - .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) - .set(state.ids.chat_char_name_button, ui); - if self.global_state.settings.interface.chat_character_name != chat_char_name { - events.push(ChatCharName( - !self.global_state.settings.interface.chat_character_name, - )); - } - Text::new( - &self - .localized_strings - .get("hud.settings.chat_character_name"), - ) - .right_from(state.ids.chat_char_name_button, 20.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.chat_char_name_text, ui); - - // TODO Show account name in chat - // Reset the interface settings to the default settings if Button::image(self.imgs.button) .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT) diff --git a/voxygen/src/hud/settings_window/mod.rs b/voxygen/src/hud/settings_window/mod.rs index 9b6388c514..8ef0a31c3e 100644 --- a/voxygen/src/hud/settings_window/mod.rs +++ b/voxygen/src/hud/settings_window/mod.rs @@ -1,3 +1,4 @@ +mod chat; mod controls; mod gameplay; mod interface; @@ -38,6 +39,7 @@ widget_ids! { video, sound, language, + chat, } } @@ -47,6 +49,7 @@ const RESET_BUTTONS_WIDTH: f64 = 155.0; #[derive(Debug, EnumIter, PartialEq)] pub enum SettingsTab { Interface, + Chat, Video, Sound, Gameplay, @@ -57,6 +60,7 @@ impl SettingsTab { fn name_key(&self) -> &str { match self { SettingsTab::Interface => "common.interface", + SettingsTab::Chat => "common.chat", SettingsTab::Gameplay => "common.gameplay", SettingsTab::Controls => "common.controls", SettingsTab::Video => "common.video", @@ -68,6 +72,7 @@ impl SettingsTab { fn title_key(&self) -> &str { match self { SettingsTab::Interface => "common.interface_settings", + SettingsTab::Chat => "common.chat_settings", SettingsTab::Gameplay => "common.gameplay_settings", SettingsTab::Controls => "common.controls_settings", SettingsTab::Video => "common.video_settings", @@ -118,6 +123,7 @@ pub enum Event { ChangeTab(SettingsTab), Close, SettingsChange(SettingsChange), + ChangeChatSettingsTab(Option), } #[derive(Clone)] @@ -250,6 +256,23 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::SettingsChange(change.into())); } }, + SettingsTab::Chat => { + for event in + chat::Chat::new(global_state, self.show, imgs, fonts, localized_strings) + .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0) + .wh_of(state.ids.settings_content_align) + .set(state.ids.chat, ui) + { + match event { + chat::Event::ChatChange(change) => { + events.push(Event::SettingsChange(change.into())); + }, + chat::Event::ChangeChatSettingsTab(index) => { + events.push(Event::ChangeChatSettingsTab(index)); + }, + } + } + }, SettingsTab::Gameplay => { for change in gameplay::Gameplay::new(global_state, imgs, fonts, localized_strings) .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0) diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index 9426e798ae..db4cc70b0e 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -2,14 +2,14 @@ use super::SessionState; use crate::{ controller::ControllerSettings, hud::{ - BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ScaleChange, + BarNumbers, BuffPosition, ChatTab, CrosshairType, Intro, PressBehavior, ScaleChange, ShortcutNumbers, XpBar, }, i18n::{LanguageMetadata, LocalizationHandle}, render::RenderMode, settings::{ - AudioSettings, ControlSettings, Fps, GamepadSettings, GameplaySettings, GraphicsSettings, - InterfaceSettings, + AudioSettings, ChatSettings, ControlSettings, Fps, GamepadSettings, GameplaySettings, + GraphicsSettings, InterfaceSettings, }, window::{FullScreenSettings, GameInput}, GlobalState, @@ -26,6 +26,17 @@ pub enum Audio { ResetAudioSettings, } #[derive(Clone)] +pub enum Chat { + Transp(f32), + CharName(bool), + ChangeChatTab(Option), + ChatTabUpdate(usize, ChatTab), + ChatTabInsert(usize, ChatTab), + ChatTabMove(usize, usize), //(i, j) move item from position i, and insert into position j + ChatTabRemove(usize), + ResetChatSettings, +} +#[derive(Clone)] pub enum Control { ChangeBinding(GameInput), ResetKeyBindings, @@ -88,8 +99,6 @@ pub enum Interface { ToggleTips(bool), CrosshairTransp(f32), - ChatTransp(f32), - ChatCharName(bool), CrosshairType(CrosshairType), Intro(Intro), ToggleXpBar(XpBar), @@ -127,6 +136,7 @@ pub enum Networking {} #[derive(Clone)] pub enum SettingsChange { Audio(Audio), + Chat(Chat), Control(Control), Gamepad(Gamepad), Gameplay(Gameplay), @@ -144,6 +154,7 @@ macro_rules! settings_change_from { }; } settings_change_from!(Audio); +settings_change_from!(Chat); settings_change_from!(Control); settings_change_from!(Gamepad); settings_change_from!(Gameplay); @@ -190,6 +201,46 @@ impl SettingsChange { } settings.save_to_file_warn(); }, + SettingsChange::Chat(chat_change) => { + let chat_tabs = &mut settings.chat.chat_tabs; + match chat_change { + Chat::Transp(chat_transp) => { + settings.chat.chat_transp = chat_transp; + }, + Chat::CharName(chat_char_name) => { + settings.chat.chat_character_name = chat_char_name; + }, + Chat::ChangeChatTab(chat_tab_index) => { + settings.chat.chat_tab_index = + chat_tab_index.filter(|i| *i < chat_tabs.len()); + }, + Chat::ChatTabUpdate(i, chat_tab) => { + if i < chat_tabs.len() { + chat_tabs[i] = chat_tab; + } + }, + Chat::ChatTabInsert(i, chat_tab) => { + if i <= chat_tabs.len() { + settings.chat.chat_tabs.insert(i, chat_tab); + } + }, + Chat::ChatTabMove(i, j) => { + if i < chat_tabs.len() && j < chat_tabs.len() { + let chat_tab = settings.chat.chat_tabs.remove(i); + settings.chat.chat_tabs.insert(j, chat_tab); + } + }, + Chat::ChatTabRemove(i) => { + if i < chat_tabs.len() { + settings.chat.chat_tabs.remove(i); + } + }, + Chat::ResetChatSettings => { + settings.chat = ChatSettings::default(); + }, + } + settings.save_to_file_warn(); + }, SettingsChange::Control(control_change) => match control_change { Control::ChangeBinding(game_input) => { global_state.window.set_keybinding_mode(game_input); @@ -400,12 +451,6 @@ impl SettingsChange { Interface::CrosshairTransp(crosshair_transp) => { settings.interface.crosshair_transp = crosshair_transp; }, - Interface::ChatTransp(chat_transp) => { - settings.interface.chat_transp = chat_transp; - }, - Interface::ChatCharName(chat_char_name) => { - settings.interface.chat_character_name = chat_char_name; - }, Interface::CrosshairType(crosshair_type) => { settings.interface.crosshair_type = crosshair_type; }, diff --git a/voxygen/src/settings/chat.rs b/voxygen/src/settings/chat.rs new file mode 100644 index 0000000000..74a7758d79 --- /dev/null +++ b/voxygen/src/settings/chat.rs @@ -0,0 +1,88 @@ +use crate::hud::ChatTab; +use common::{ + comp::{ChatMsg, ChatType}, + uid::Uid, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +pub const MAX_CHAT_TABS: usize = 5; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ChatFilter { + //messages + pub message_all: bool, + pub message_world: bool, + pub message_region: bool, + pub message_say: bool, + pub message_group: bool, + pub message_faction: bool, + //activity (login/logout) + pub activity_all: bool, + pub activity_group: bool, + //deaths + pub death_all: bool, + pub death_group: bool, +} +impl ChatFilter { + pub fn satisfies(&self, chat_msg: &ChatMsg, group_members: &HashSet<&Uid>) -> bool { + match &chat_msg.chat_type { + ChatType::Online(u) | ChatType::Offline(u) => { + self.activity_all || (self.activity_group && group_members.contains(u)) + }, + ChatType::CommandInfo | ChatType::CommandError => true, + ChatType::Kill(_, u) => self.death_all || self.death_group && group_members.contains(u), + ChatType::GroupMeta(_) => true, //todo + ChatType::FactionMeta(_) => true, //todo + ChatType::Tell(..) => true, + ChatType::Say(_) => self.message_all || self.message_say, + ChatType::Group(..) => self.message_all || self.message_group, + ChatType::Faction(..) => self.message_all || self.message_faction, + ChatType::Region(_) => self.message_all || self.message_region, + ChatType::World(_) => self.message_all || self.message_world, + ChatType::Npc(..) => true, + ChatType::NpcSay(..) => true, + ChatType::NpcTell(..) => true, + ChatType::Meta => true, + ChatType::Loot => true, + } + } +} +impl Default for ChatFilter { + fn default() -> Self { + Self { + message_all: true, + message_world: true, + message_region: true, + message_say: true, + message_group: true, + message_faction: true, + + activity_all: false, + activity_group: true, + + death_all: false, + death_group: true, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct ChatSettings { + pub chat_transp: f32, + pub chat_character_name: bool, + pub chat_tabs: Vec, + pub chat_tab_index: Option, +} + +impl Default for ChatSettings { + fn default() -> Self { + Self { + chat_transp: 0.4, + chat_character_name: true, + chat_tabs: vec![ChatTab::default()], + chat_tab_index: Some(0), + } + } +} diff --git a/voxygen/src/settings/interface.rs b/voxygen/src/settings/interface.rs index 9e2a96472d..4e898a9977 100644 --- a/voxygen/src/settings/interface.rs +++ b/voxygen/src/settings/interface.rs @@ -16,8 +16,6 @@ pub struct InterfaceSettings { pub speech_bubble_dark_mode: bool, pub speech_bubble_icon: bool, pub crosshair_transp: f32, - pub chat_transp: f32, - pub chat_character_name: bool, pub crosshair_type: CrosshairType, pub intro_show: Intro, pub xp_bar: XpBar, @@ -51,8 +49,6 @@ impl Default for InterfaceSettings { speech_bubble_dark_mode: false, speech_bubble_icon: true, crosshair_transp: 0.6, - chat_transp: 0.4, - chat_character_name: true, crosshair_type: CrosshairType::Round, intro_show: Intro::Show, xp_bar: XpBar::Always, diff --git a/voxygen/src/settings/mod.rs b/voxygen/src/settings/mod.rs index 3566acbfee..6790526ff2 100644 --- a/voxygen/src/settings/mod.rs +++ b/voxygen/src/settings/mod.rs @@ -3,16 +3,18 @@ use serde::{Deserialize, Serialize}; use std::{fs, path::PathBuf}; use tracing::warn; -mod audio; -mod control; -mod gamepad; -mod gameplay; -mod graphics; -mod interface; -mod language; -mod networking; +pub mod audio; +pub mod chat; +pub mod control; +pub mod gamepad; +pub mod gameplay; +pub mod graphics; +pub mod interface; +pub mod language; +pub mod networking; pub use audio::{AudioOutput, AudioSettings}; +pub use chat::ChatSettings; pub use control::ControlSettings; pub use gamepad::GamepadSettings; pub use gameplay::GameplaySettings; @@ -60,6 +62,7 @@ impl Default for Log { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Settings { + pub chat: ChatSettings, pub controls: ControlSettings, pub interface: InterfaceSettings, pub gameplay: GameplaySettings, @@ -96,6 +99,7 @@ impl Default for Settings { .expect("Couldn't choose a place to store the screenshots"); Settings { + chat: ChatSettings::default(), controls: ControlSettings::default(), interface: InterfaceSettings::default(), gameplay: GameplaySettings::default(),