Added chat tabs

This commit is contained in:
hqurve 2021-05-14 12:27:15 +00:00 committed by Marcel
parent e66a2079ce
commit 95a6e35a3a
19 changed files with 1177 additions and 218 deletions

View File

@ -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 - /skill_preset command which allows you to apply skill presets
- Added timed bans and ban history. - Added timed bans and ban history.
- Added non-admin moderators with limit privileges and updated the security model to reflect this. - Added non-admin moderators with limit privileges and updated the security model to reflect this.
- Chat tabs
### Changed ### Changed

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/ui/settings/chat_tab_settings_bg.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -16,6 +16,7 @@
"common.controls": "Controls", "common.controls": "Controls",
"common.video": "Graphics", "common.video": "Graphics",
"common.sound": "Sound", "common.sound": "Sound",
"common.chat": "Chat",
"common.resume": "Resume", "common.resume": "Resume",
"common.characters": "Characters", "common.characters": "Characters",
"common.close": "Close", "common.close": "Close",
@ -44,6 +45,7 @@
"common.video_settings": "Graphics Settings", "common.video_settings": "Graphics Settings",
"common.sound_settings": "Sound Settings", "common.sound_settings": "Sound Settings",
"common.language_settings": "Language Settings", "common.language_settings": "Language Settings",
"common.chat_settings": "Chat Settings",
// Message when connection to the server is lost // Message when connection to the server is lost
"common.connection_lost": r#"Connection lost! "common.connection_lost": r#"Connection lost!

View File

@ -3,6 +3,9 @@
/// Localization for "global" English /// Localization for "global" English
( (
string_map: { string_map: {
"hud.chat.all": "All",
"hud.chat.chat_tab_hover_tooltip": "Right click for settings",
// Debuff outcomes // Debuff outcomes
"hud.outcome.burning": "died of: burning", "hud.outcome.burning": "died of: burning",
"hud.outcome.curse": "died of: curse", "hud.outcome.curse": "died of: curse",

View File

@ -107,6 +107,23 @@
"hud.settings.awaitingkey": "Press a key...", "hud.settings.awaitingkey": "Press a key...",
"hud.settings.unbound": "None", "hud.settings.unbound": "None",
"hud.settings.reset_keybinds": "Reset to Defaults", "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",
}, },

View File

@ -1,15 +1,18 @@
use super::{ use super::{
img_ids::Imgs, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR, LOOT_COLOR, img_ids::Imgs, ChatTab, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR,
OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, WORLD_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 client::{cmd, Client};
use common::comp::{ use common::comp::{
chat::{KillSource, KillType}, chat::{KillSource, KillType},
group::Role,
BuffKind, ChatMode, ChatMsg, ChatType, BuffKind, ChatMode, ChatMsg, ChatType,
}; };
use common_net::msg::validate_chat_msg; use common_net::msg::validate_chat_msg;
use conrod_core::{ use conrod_core::{
color,
input::Key, input::Key,
position::Dimension, position::Dimension,
text::{ text::{
@ -17,9 +20,10 @@ use conrod_core::{
cursor::{self, Index}, cursor::{self, Index},
}, },
widget::{self, Button, Id, Image, List, Rectangle, Text, TextEdit}, 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! { widget_ids! {
struct Ids { struct Ids {
@ -29,7 +33,15 @@ widget_ids! {
chat_input_bg, chat_input_bg,
chat_input_icon, chat_input_icon,
chat_arrow, chat_arrow,
chat_icon_align,
chat_icons[], 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)] /*#[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_ICON_HEIGHT: f64 = 16.0;
const CHAT_BOX_WIDTH: f64 = 470.0; const CHAT_BOX_WIDTH: f64 = 470.0;
const CHAT_BOX_INPUT_WIDTH: f64 = 460.0 - CHAT_ICON_WIDTH - 1.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)] #[derive(WidgetCommon)]
pub struct Chat<'a> { pub struct Chat<'a> {
pulse: f32,
new_messages: &'a mut VecDeque<ChatMsg>, new_messages: &'a mut VecDeque<ChatMsg>,
client: &'a Client, client: &'a Client,
force_input: Option<String>, force_input: Option<String>,
@ -69,11 +85,13 @@ impl<'a> Chat<'a> {
new_messages: &'a mut VecDeque<ChatMsg>, new_messages: &'a mut VecDeque<ChatMsg>,
client: &'a Client, client: &'a Client,
global_state: &'a GlobalState, global_state: &'a GlobalState,
pulse: f32,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a Fonts,
localized_strings: &'a Localization, localized_strings: &'a Localization,
) -> Self { ) -> Self {
Self { Self {
pulse,
new_messages, new_messages,
client, client,
force_input: None, force_input: None,
@ -142,16 +160,22 @@ pub struct State {
completions_index: Option<usize>, completions_index: Option<usize>,
// At which character is tab completion happening // At which character is tab completion happening
completion_cursor: Option<usize>, completion_cursor: Option<usize>,
// last time mouse has been hovered
tabs_last_hover_pulse: Option<f32>,
// last chat_tab (used to see if chat tab has been changed)
prev_chat_tab: Option<ChatTab>,
} }
pub enum Event { pub enum Event {
TabCompletionStart(String), TabCompletionStart(String),
SendMessage(String), SendMessage(String),
Focus(Id), Focus(Id),
ChangeChatTab(Option<usize>),
ShowChatTabSettings(usize),
} }
impl<'a> Widget for Chat<'a> { impl<'a> Widget for Chat<'a> {
type Event = Option<Event>; type Event = Vec<Event>;
type State = State; type State = State;
type Style = (); type Style = ();
@ -168,6 +192,8 @@ impl<'a> Widget for Chat<'a> {
completions_index: None, completions_index: None,
completion_cursor: None, completion_cursor: None,
ids: Ids::new(id_gen), 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 #[allow(clippy::single_match)] // TODO: Pending review in #587
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, ui, .. } = args; 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. // Maintain scrolling.
if !self.new_messages.is_empty() { if !self.new_messages.is_empty() {
state.update(|s| s.messages.extend(self.new_messages.drain(..))); state.update(|s| s.messages.extend(self.new_messages.drain(..)));
ui.scroll_widget(state.ids.message_box, [0.0, std::f64::MAX]); 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 // Empty old messages
state.update(|s| { state.update(|s| {
@ -322,7 +359,7 @@ impl<'a> Widget for Chat<'a> {
_ => 0.0, _ => 0.0,
}; };
Rectangle::fill([CHAT_BOX_WIDTH, y]) 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) .bottom_left_with_margins_on(ui.window, 10.0, 10.0)
.w(CHAT_BOX_WIDTH) .w(CHAT_BOX_WIDTH)
.set(state.ids.chat_input_bg, ui); .set(state.ids.chat_input_bg, ui);
@ -341,7 +378,7 @@ impl<'a> Widget for Chat<'a> {
// Message box // Message box
Rectangle::fill([CHAT_BOX_WIDTH, CHAT_BOX_HEIGHT]) 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| { .and(|r| {
if input_focused { if input_focused {
r.up_from(state.ids.chat_input_bg, 0.0) r.up_from(state.ids.chat_input_bg, 0.0)
@ -351,11 +388,6 @@ impl<'a> Widget for Chat<'a> {
}) })
.crop_kids() .crop_kids()
.set(state.ids.message_box_bg, ui); .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() { if state.ids.chat_icons.len() < state.messages.len() {
state.update(|s| { state.update(|s| {
s.ids s.ids
@ -363,105 +395,70 @@ impl<'a> Widget for Chat<'a> {
.resize(s.messages.len(), &mut ui.widget_id_generator()) .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::<HashSet<_>>();
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::<Vec<_>>();
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) { while let Some(item) = items.next(ui) {
// This would be easier if conrod used the v-metrics from rusttype. // This would be easier if conrod used the v-metrics from rusttype.
if item.i < state.messages.len() { if item.i < messages.len() {
let mut message = state.messages[item.i].clone(); let message = &messages[item.i];
let (color, icon) = render_chat_line(&message.chat_type, &self.imgs); 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 // For each ChatType needing localization get/set matching pre-formatted
// localized string. This string will be formatted with the data // localized string. This string will be formatted with the data
// provided in ChatType in the client/src/mod.rs // provided in ChatType in the client/src/mod.rs
// fn format_message called below // fn format_message called below
message.message = match chat_type {
ChatType::Online(_) => self let text = Text::new(&message.message)
.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)
.font_size(self.fonts.opensans.scale(15)) .font_size(self.fonts.opensans.scale(15))
.font_id(self.fonts.opensans.conrod_id) .font_id(self.fonts.opensans.conrod_id)
.w(CHAT_BOX_WIDTH - 17.0) .w(CHAT_BOX_WIDTH - 17.0)
@ -477,7 +474,7 @@ impl<'a> Widget for Chat<'a> {
Image::new(icon) Image::new(icon)
.w_h(CHAT_ICON_WIDTH, CHAT_ICON_HEIGHT) .w_h(CHAT_ICON_WIDTH, CHAT_ICON_HEIGHT)
.top_left_with_margins_on(item.widget_id, 2.0, -CHAT_ICON_WIDTH) .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); .set(icon_id, ui);
} else { } else {
// Spacer at bottom of the last message so that it is not cut off. // 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 // Chat Arrow
// Check if already at bottom. // Check if already at bottom.
if !Self::scrolled_to_bottom(state, ui) 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. // We've started a new tab completion. Populate tab completion suggestions.
if request_tab_completions { 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 // If the chat widget is focused, return a focus event to pass the focus
// to the input box. // to the input box.
} else if keyboard_capturer == Some(id) { } 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. // 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( 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); s.history.truncate(self.history_max);
} }
}); });
Some(Event::SendMessage(msg)) events.push(Event::SendMessage(msg));
} else {
None
} }
events
} }
} }
@ -646,3 +761,30 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat
template.replace("{died_of_buff}", buff_outcome) template.replace("{died_of_buff}", buff_outcome)
} }
fn get_chat_template_key(chat_type: &ChatType<String>) -> 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,
})
}

View File

@ -122,6 +122,13 @@ image_ids! {
settings_button_hover: "voxygen.element.ui.settings.buttons.settings_button_hover", settings_button_hover: "voxygen.element.ui.settings.buttons.settings_button_hover",
settings_button_press: "voxygen.element.ui.settings.buttons.settings_button_press", 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", quest_bg: "voxygen.element.ui.quests.temp_quest_bg",
// Slider // Slider

View File

@ -55,9 +55,10 @@ use crate::{
render::{Consts, Globals, Renderer}, render::{Consts, Globals, Renderer},
scene::camera::{self, Camera}, scene::camera::{self, Camera},
session::{ session::{
settings_change::{Interface as InterfaceChange, SettingsChange}, settings_change::{Chat as ChatChange, Interface as InterfaceChange, SettingsChange},
Interactable, Interactable,
}, },
settings::chat::ChatFilter,
ui::{ ui::{
fonts::Fonts, img_ids::Rotations, slot, slot::SlotKey, Graphic, Ingameable, ScaleMode, Ui, fonts::Fonts, img_ids::Rotations, slot, slot::SlotKey, Graphic, Ingameable, ScaleMode, Ui,
}, },
@ -466,6 +467,19 @@ pub enum PressBehavior {
#[serde(other)] #[serde(other)]
Toggle = 0, 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 { impl PressBehavior {
pub fn update(&self, keystate: bool, setting: &mut bool, f: impl FnOnce(bool)) { pub fn update(&self, keystate: bool, setting: &mut bool, f: impl FnOnce(bool)) {
@ -503,6 +517,7 @@ pub struct Show {
open_windows: Windows, open_windows: Windows,
map: bool, map: bool,
ingame: bool, ingame: bool,
chat_tab_settings_index: Option<usize>,
settings_tab: SettingsTab, settings_tab: SettingsTab,
skilltreetab: SelectedSkillTree, skilltreetab: SelectedSkillTree,
crafting_tab: CraftingTab, crafting_tab: CraftingTab,
@ -869,6 +884,7 @@ impl Hud {
diary: false, diary: false,
group: false, group: false,
group_menu: false, group_menu: false,
chat_tab_settings_index: None,
settings_tab: SettingsTab::Interface, settings_tab: SettingsTab::Interface,
skilltreetab: SelectedSkillTree::General, skilltreetab: SelectedSkillTree::General,
crafting_tab: CraftingTab::All, crafting_tab: CraftingTab::All,
@ -2585,10 +2601,11 @@ impl Hud {
.retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_, _))); .retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_, _)));
// Chat box // Chat box
match Chat::new( for event in Chat::new(
&mut self.new_messages, &mut self.new_messages,
&client, &client,
global_state, global_state,
self.pulse,
&self.imgs, &self.imgs,
&self.fonts, &self.fonts,
i18n, i18n,
@ -2600,16 +2617,25 @@ impl Hud {
.and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos)) .and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos))
.set(self.ids.chat, ui_widgets) .set(self.ids.chat, ui_widgets)
{ {
Some(chat::Event::TabCompletionStart(input)) => { match event {
chat::Event::TabCompletionStart(input) => {
self.tab_complete = Some(input); self.tab_complete = Some(input);
}, },
Some(chat::Event::SendMessage(message)) => { chat::Event::SendMessage(message) => {
events.push(Event::SendMessage(message)); events.push(Event::SendMessage(message));
}, },
Some(chat::Event::Focus(focus_id)) => { chat::Event::Focus(focus_id) => {
self.to_focus = Some(Some(focus_id)); self.to_focus = Some(Some(focus_id));
}, },
None => {}, 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(); self.new_messages = VecDeque::new();
@ -2644,6 +2670,9 @@ impl Hud {
self.show.settings(false) self.show.settings(false)
}, },
settings_window::Event::ChangeChatSettingsTab(tab) => {
self.show.chat_tab_settings_index = tab;
},
settings_window::Event::SettingsChange(settings_change) => { settings_window::Event::SettingsChange(settings_change) => {
match &settings_change { match &settings_change {
SettingsChange::Interface(interface_change) => match interface_change { SettingsChange::Interface(interface_change) => match interface_change {

View File

@ -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<usize>),
ChatChange(ChatChange),
}
impl<'a> Widget for Chat<'a> {
type Event = Vec<Event>;
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>) -> 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
}
}

View File

@ -66,12 +66,6 @@ widget_ids! {
buff_pos_map_button, buff_pos_map_button,
buff_pos_map_text, buff_pos_map_text,
// //
chat_transp_title,
chat_transp_text,
chat_transp_slider,
chat_char_name_text,
chat_char_name_button,
//
sct_title, sct_title,
sct_show_text, sct_show_text,
sct_show_radio, 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_transp = self.global_state.settings.interface.crosshair_transp;
let crosshair_type = self.global_state.settings.interface.crosshair_type; let crosshair_type = self.global_state.settings.interface.crosshair_type;
let ui_scale = self.global_state.settings.interface.ui_scale; 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")) Text::new(&self.localized_strings.get("hud.settings.general"))
.top_left_with_margins_on(state.ids.window, 5.0, 5.0) .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) .color(TEXT_COLOR)
.set(state.ids.show_bar_numbers_percentage_text, ui); .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 // Reset the interface settings to the default settings
if Button::image(self.imgs.button) if Button::image(self.imgs.button)
.w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT) .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)

View File

@ -1,3 +1,4 @@
mod chat;
mod controls; mod controls;
mod gameplay; mod gameplay;
mod interface; mod interface;
@ -38,6 +39,7 @@ widget_ids! {
video, video,
sound, sound,
language, language,
chat,
} }
} }
@ -47,6 +49,7 @@ const RESET_BUTTONS_WIDTH: f64 = 155.0;
#[derive(Debug, EnumIter, PartialEq)] #[derive(Debug, EnumIter, PartialEq)]
pub enum SettingsTab { pub enum SettingsTab {
Interface, Interface,
Chat,
Video, Video,
Sound, Sound,
Gameplay, Gameplay,
@ -57,6 +60,7 @@ impl SettingsTab {
fn name_key(&self) -> &str { fn name_key(&self) -> &str {
match self { match self {
SettingsTab::Interface => "common.interface", SettingsTab::Interface => "common.interface",
SettingsTab::Chat => "common.chat",
SettingsTab::Gameplay => "common.gameplay", SettingsTab::Gameplay => "common.gameplay",
SettingsTab::Controls => "common.controls", SettingsTab::Controls => "common.controls",
SettingsTab::Video => "common.video", SettingsTab::Video => "common.video",
@ -68,6 +72,7 @@ impl SettingsTab {
fn title_key(&self) -> &str { fn title_key(&self) -> &str {
match self { match self {
SettingsTab::Interface => "common.interface_settings", SettingsTab::Interface => "common.interface_settings",
SettingsTab::Chat => "common.chat_settings",
SettingsTab::Gameplay => "common.gameplay_settings", SettingsTab::Gameplay => "common.gameplay_settings",
SettingsTab::Controls => "common.controls_settings", SettingsTab::Controls => "common.controls_settings",
SettingsTab::Video => "common.video_settings", SettingsTab::Video => "common.video_settings",
@ -118,6 +123,7 @@ pub enum Event {
ChangeTab(SettingsTab), ChangeTab(SettingsTab),
Close, Close,
SettingsChange(SettingsChange), SettingsChange(SettingsChange),
ChangeChatSettingsTab(Option<usize>),
} }
#[derive(Clone)] #[derive(Clone)]
@ -250,6 +256,23 @@ impl<'a> Widget for SettingsWindow<'a> {
events.push(Event::SettingsChange(change.into())); 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 => { SettingsTab::Gameplay => {
for change in gameplay::Gameplay::new(global_state, imgs, fonts, localized_strings) 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) .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)

View File

@ -2,14 +2,14 @@ use super::SessionState;
use crate::{ use crate::{
controller::ControllerSettings, controller::ControllerSettings,
hud::{ hud::{
BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ScaleChange, BarNumbers, BuffPosition, ChatTab, CrosshairType, Intro, PressBehavior, ScaleChange,
ShortcutNumbers, XpBar, ShortcutNumbers, XpBar,
}, },
i18n::{LanguageMetadata, LocalizationHandle}, i18n::{LanguageMetadata, LocalizationHandle},
render::RenderMode, render::RenderMode,
settings::{ settings::{
AudioSettings, ControlSettings, Fps, GamepadSettings, GameplaySettings, GraphicsSettings, AudioSettings, ChatSettings, ControlSettings, Fps, GamepadSettings, GameplaySettings,
InterfaceSettings, GraphicsSettings, InterfaceSettings,
}, },
window::{FullScreenSettings, GameInput}, window::{FullScreenSettings, GameInput},
GlobalState, GlobalState,
@ -26,6 +26,17 @@ pub enum Audio {
ResetAudioSettings, ResetAudioSettings,
} }
#[derive(Clone)] #[derive(Clone)]
pub enum Chat {
Transp(f32),
CharName(bool),
ChangeChatTab(Option<usize>),
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 { pub enum Control {
ChangeBinding(GameInput), ChangeBinding(GameInput),
ResetKeyBindings, ResetKeyBindings,
@ -88,8 +99,6 @@ pub enum Interface {
ToggleTips(bool), ToggleTips(bool),
CrosshairTransp(f32), CrosshairTransp(f32),
ChatTransp(f32),
ChatCharName(bool),
CrosshairType(CrosshairType), CrosshairType(CrosshairType),
Intro(Intro), Intro(Intro),
ToggleXpBar(XpBar), ToggleXpBar(XpBar),
@ -127,6 +136,7 @@ pub enum Networking {}
#[derive(Clone)] #[derive(Clone)]
pub enum SettingsChange { pub enum SettingsChange {
Audio(Audio), Audio(Audio),
Chat(Chat),
Control(Control), Control(Control),
Gamepad(Gamepad), Gamepad(Gamepad),
Gameplay(Gameplay), Gameplay(Gameplay),
@ -144,6 +154,7 @@ macro_rules! settings_change_from {
}; };
} }
settings_change_from!(Audio); settings_change_from!(Audio);
settings_change_from!(Chat);
settings_change_from!(Control); settings_change_from!(Control);
settings_change_from!(Gamepad); settings_change_from!(Gamepad);
settings_change_from!(Gameplay); settings_change_from!(Gameplay);
@ -190,6 +201,46 @@ impl SettingsChange {
} }
settings.save_to_file_warn(); 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 { SettingsChange::Control(control_change) => match control_change {
Control::ChangeBinding(game_input) => { Control::ChangeBinding(game_input) => {
global_state.window.set_keybinding_mode(game_input); global_state.window.set_keybinding_mode(game_input);
@ -400,12 +451,6 @@ impl SettingsChange {
Interface::CrosshairTransp(crosshair_transp) => { Interface::CrosshairTransp(crosshair_transp) => {
settings.interface.crosshair_transp = 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) => { Interface::CrosshairType(crosshair_type) => {
settings.interface.crosshair_type = crosshair_type; settings.interface.crosshair_type = crosshair_type;
}, },

View File

@ -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<ChatTab>,
pub chat_tab_index: Option<usize>,
}
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),
}
}
}

View File

@ -16,8 +16,6 @@ pub struct InterfaceSettings {
pub speech_bubble_dark_mode: bool, pub speech_bubble_dark_mode: bool,
pub speech_bubble_icon: bool, pub speech_bubble_icon: bool,
pub crosshair_transp: f32, pub crosshair_transp: f32,
pub chat_transp: f32,
pub chat_character_name: bool,
pub crosshair_type: CrosshairType, pub crosshair_type: CrosshairType,
pub intro_show: Intro, pub intro_show: Intro,
pub xp_bar: XpBar, pub xp_bar: XpBar,
@ -51,8 +49,6 @@ impl Default for InterfaceSettings {
speech_bubble_dark_mode: false, speech_bubble_dark_mode: false,
speech_bubble_icon: true, speech_bubble_icon: true,
crosshair_transp: 0.6, crosshair_transp: 0.6,
chat_transp: 0.4,
chat_character_name: true,
crosshair_type: CrosshairType::Round, crosshair_type: CrosshairType::Round,
intro_show: Intro::Show, intro_show: Intro::Show,
xp_bar: XpBar::Always, xp_bar: XpBar::Always,

View File

@ -3,16 +3,18 @@ use serde::{Deserialize, Serialize};
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use tracing::warn; use tracing::warn;
mod audio; pub mod audio;
mod control; pub mod chat;
mod gamepad; pub mod control;
mod gameplay; pub mod gamepad;
mod graphics; pub mod gameplay;
mod interface; pub mod graphics;
mod language; pub mod interface;
mod networking; pub mod language;
pub mod networking;
pub use audio::{AudioOutput, AudioSettings}; pub use audio::{AudioOutput, AudioSettings};
pub use chat::ChatSettings;
pub use control::ControlSettings; pub use control::ControlSettings;
pub use gamepad::GamepadSettings; pub use gamepad::GamepadSettings;
pub use gameplay::GameplaySettings; pub use gameplay::GameplaySettings;
@ -60,6 +62,7 @@ impl Default for Log {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Settings { pub struct Settings {
pub chat: ChatSettings,
pub controls: ControlSettings, pub controls: ControlSettings,
pub interface: InterfaceSettings, pub interface: InterfaceSettings,
pub gameplay: GameplaySettings, pub gameplay: GameplaySettings,
@ -96,6 +99,7 @@ impl Default for Settings {
.expect("Couldn't choose a place to store the screenshots"); .expect("Couldn't choose a place to store the screenshots");
Settings { Settings {
chat: ChatSettings::default(),
controls: ControlSettings::default(), controls: ControlSettings::default(),
interface: InterfaceSettings::default(), interface: InterfaceSettings::default(),
gameplay: GameplaySettings::default(), gameplay: GameplaySettings::default(),