Merge branch 'snowram/items-infos' into 'master'

Snowram/items infos

See merge request veloren/veloren!1956
This commit is contained in:
Snowram 2021-03-29 20:16:35 +00:00
commit 88f99986af
26 changed files with 1867 additions and 720 deletions

View File

@ -24,9 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Separated character randomization buttons into appearance and name.
- Reworked mindflayer to have unique attacks
- Glowing remains are now `Armor` instead of `Ingredients`.
- Generated a net world map
- Overhauled clouds for more verticality and performance
- New tooltip for items with stats comparison
### Removed

View File

@ -7,8 +7,8 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.0,
power: 1000.0,
poise_strength: 1000.0,
power: 999.9,
poise_strength: 999.9,
speed: 1.0,
crit_chance: 0.5,
crit_mult: 2.0,

Binary file not shown.

View File

@ -24,6 +24,14 @@
"buff.desc.bleed": "Inflicts regular damage.",
"buff.title.cursed": "Cursed",
"buff.desc.cursed": "You are cursed.",
// Buffs stats
"buff.stat.health": "Restores {str_total} Health",
"buff.stat.increase_max_stamina": "Raises Maximum Stamina by {strength}",
"buff.stat.increase_max_health": "Raises Maximum Health by {strength}",
"buff.stat.invulnerability": "Grants invulnerability",
// Text
"buff.text.over_seconds": "over {dur_secs} seconds",
"buff.text.for_seconds": "for {dur_secs} seconds",
},

View File

@ -35,6 +35,7 @@
"common.you": "You",
"common.automatic": "Auto",
"common.random": "Random",
"common.empty": "Empty",
// Settings Window title
"common.interface_settings": "Interface Settings",
@ -64,8 +65,39 @@ Is the client up to date?"#,
"common.weapons.hammer": "Hammer",
"common.weapons.general": "General Combat",
"common.weapons.sceptre": "Healing Sceptre",
"common.weapons.sceptre": "Shield",
"common.weapons.spear": "Spear",
"common.weapons.hammer_simple": "Simple Hammer",
"common.weapons.sword_simple": "Simple Sword",
"common.weapons.staff_simple": "Simple Staff",
"common.weapons.axe_simple": "Simple Axe",
"common.weapons.bow_simple": "Simple Bow",
"common.weapons.unique": "Unique",
"common.tool.debug": "Debug",
"common.tool.faming": "Farming Tool",
"common.tool.pick": "Pickaxe",
"common.kind.modular_component": "Modular Component",
"common.kind.glider": "Glider",
"common.kind.consumable": "Consumable",
"common.kind.throwable": "Can be thrown",
"common.kind.utility": "Utility",
"common.kind.ingredient": "Ingredient",
"common.kind.lantern": "Lantern",
"common.hands.one": "One-Handed",
"common.hands.two": "Two-Handed",
"common.rand_appearance": "Random appearance",
"common.rand_name": "Random name",
"common.stats.dps": "DPS",
"common.stats.power": "Power",
"common.stats.speed": "Speed",
"common.stats.poise": "Poise",
"common.stats.crit_chance": "Crit Chance",
"common.stats.crit_mult": "Crit Mult",
"common.stats.armor": "Armor",
"common.stats.poise_res": "Poise res",
"common.stats.slots": "Slots",
},

View File

@ -25,6 +25,12 @@
"hud.bag.mainhand": "Mainhand",
"hud.bag.offhand": "Offhand",
"hud.bag.bag": "Bag",
"hud.bag.health": "Health",
"hud.bag.stamina": "Stamina",
"hud.bag.combat_rating": "Combat Rating",
"hud.bag.protection": "Protection",
"hud.bag.combat_rating_desc": "Calculated from your\nequipment and health.",
"hud.bag.protection": "Damage reduction through armor",
},

View File

@ -16,6 +16,9 @@
"hud.trade.result.completed": "Trade completed successfully.",
"hud.trade.result.declined": "Trade declined.",
"hud.trade.result.nospace": "Not enough space to complete the trade.",
"hud.trade.buy_price": "Buy price",
"hud.trade.sell_price": "Sell Price",
"hud.trade.coin": "coin(s)",
},

View File

@ -24,6 +24,15 @@
"buff.desc.bleed": "Inflige régulièrement des dommages.",
"buff.title.cursed": "Maudit",
"buff.desc.cursed": "Vous êtes maudit.",
// Buffs stats
"buff.stat.health": "Restaure {str_total} points de vie",
"buff.stat.increase_max_stamina": "Augmente la vigueur maximale de {strength}",
"buff.stat.increase_max_health": "Augmente la santé maximale de {strength}",
"buff.stat.invulnerability": "Rend invincible",
// Text
"buff.text.over_seconds": "pendant {dur_secs} secondes",
"buff.text.for_seconds": "pour {dur_secs} secondes",
"buff.text.every_second": "chaque seconde",
},

View File

@ -34,6 +34,8 @@
"common.you": "Toi",
"common.automatic": "Auto",
"common.random": "Aléatoire",
"common.empty": "Vide",
// Settings Window title
"common.interface_settings": "Options de l'interface",
"common.gameplay_settings": "Options de gameplay",
@ -62,7 +64,38 @@ Le client est-il à jour?"#,
"common.weapons.bow": "Arc",
"common.weapons.hammer": "Marteau",
"common.weapons.sceptre": "Sceptre de soin",
"common.weapons.sceptre": "Bouclier",
"common.weapons.spear": "Lance",
"common.weapons.hammer_simple": "Marteau Simple",
"common.weapons.sword_simple": "Épée Simple",
"common.weapons.staff_simple": "Bâton Simple",
"common.weapons.axe_simple": "Hache Simple",
"common.weapons.bow_simple": "Arc Simple",
"common.weapons.unique": "Unique",
"common.tool.debug": "Debug",
"common.tool.faming": "Outil agricole",
"common.tool.pick": "Pioche",
"common.kind.modular_component": "Composant Modulaire",
"common.kind.glider": "Planeur",
"common.kind.consumable": "Consommable",
"common.kind.throwable": "Peut être lancé",
"common.kind.utility": "Utilitaire",
"common.kind.ingredient": "Ingrédient",
"common.kind.lantern": "Lanterne",
"common.hands.one": "Une main",
"common.hands.two": "Deux mains",
"common.rand_appearance": "Apparence et nom aléatoire",
"common.stats.dps": "DPS",
"common.stats.power": "Puissance",
"common.stats.speed": "Vitesse",
"common.stats.poise": "Impact",
"common.stats.crit_chance": "Chance Crit",
"common.stats.crit_mult": "Mult Crit",
"common.stats.armor": "Armure",
"common.stats.poise_res": "Res Impact",
"common.stats.slots": "Emplacements",
},

View File

@ -25,6 +25,12 @@
"hud.bag.mainhand": "Main Dominante",
"hud.bag.offhand": "Main Secondaire",
"hud.bag.bag": "Sac",
"hud.bag.health": "Santé",
"hud.bag.stamina": "Vigueur",
"hud.bag.combat_rating": "Niveau de Combat",
"hud.bag.protection": "Protection",
"hud.bag.combat_rating_desc": "Calculé depuis votre\néquipement et votre santé",
"hud.bag.protection_desc": "Réduction des dégats de votre armure",
},

View File

@ -16,6 +16,9 @@
"hud.trade.result.completed": "Échange complété avec succès.",
"hud.trade.result.declined": "Échange décliné.",
"hud.trade.result.nospace": "Pas assez d'espace libre pour compléter l'échange.",
"hud.trade.buy_price": "Prix d'achat",
"hud.trade.sell_price": "Prix de vente",
"hud.trade.coin": "pièce(s)",
},

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, ops::Sub};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ArmorKind {
@ -40,6 +41,21 @@ impl Stats {
poise_resilience,
}
}
pub fn get_protection(&self) -> Protection { self.protection }
pub fn get_poise_resilience(&self) -> Protection { self.poise_resilience }
}
impl Sub<Stats> for Stats {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self {
protection: self.protection - other.protection,
poise_resilience: self.poise_resilience - other.poise_resilience,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@ -48,6 +64,31 @@ pub enum Protection {
Normal(f32),
}
impl Sub for Protection {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
let diff = match (self, other) {
(Protection::Invincible, Protection::Normal(_)) => f32::INFINITY,
(Protection::Invincible, Protection::Invincible) => 0_f32,
(Protection::Normal(_), Protection::Invincible) => -f32::INFINITY,
(Protection::Normal(a), Protection::Normal(b)) => a - b,
};
Protection::Normal(diff)
}
}
impl PartialOrd for Protection {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (*self, *other) {
(Protection::Invincible, Protection::Invincible) => Some(Ordering::Equal),
(Protection::Invincible, _) => Some(Ordering::Greater),
(_, Protection::Invincible) => Some(Ordering::Less),
(Protection::Normal(a), Protection::Normal(b)) => f32::partial_cmp(&a, &b),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Armor {
pub kind: ArmorKind,

View File

@ -728,3 +728,21 @@ pub struct ItemDrop(pub Item);
impl Component for ItemDrop {
type Storage = IdvStorage<Self>;
}
impl<'a, T: ItemDesc + ?Sized> ItemDesc for &'a T {
fn description(&self) -> &str { (*self).description() }
fn name(&self) -> &str { (*self).name() }
fn kind(&self) -> &ItemKind { (*self).kind() }
fn quality(&self) -> &Quality { (*self).quality() }
fn num_slots(&self) -> u16 { (*self).num_slots() }
fn item_definition_id(&self) -> &str { (*self).item_definition_id() }
fn components(&self) -> &[Item] { (*self).components() }
fn tags(&self) -> &[ItemTag] { (*self).tags() }
}

View File

@ -8,7 +8,7 @@ use crate::{
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{
ops::{AddAssign, DivAssign, MulAssign},
ops::{AddAssign, DivAssign, MulAssign, Sub},
time::Duration,
};
use tracing::error;
@ -139,6 +139,21 @@ impl DivAssign<usize> for Stats {
}
}
impl Sub<Stats> for Stats {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self {
equip_time_secs: self.equip_time_secs - other.equip_time_secs,
power: self.power - other.power,
poise_strength: self.poise_strength - other.poise_strength,
speed: self.speed - other.speed,
crit_chance: self.crit_chance - other.crit_chance,
crit_mult: self.crit_mult - other.crit_mult,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MaterialStatManifest(pub HashMap<String, Stats>);

View File

@ -206,6 +206,18 @@ impl Loadout {
.or_else(|| first.map(|x| x.equip_slot))
}
/// Returns all items currently equipped that an item of the given ItemKind
/// could replace
pub(super) fn equipped_items_of_kind(
&self,
item_kind: ItemKind,
) -> impl Iterator<Item = &Item> {
self.slots
.iter()
.filter(move |s| s.equip_slot.can_hold(&item_kind))
.filter_map(|s| s.slot.as_ref())
}
/// Returns the `InvSlot` for a given `LoadoutSlotId`
pub(super) fn inv_slot(&self, loadout_slot_id: LoadoutSlotId) -> Option<&InvSlot> {
self.slots

View File

@ -9,7 +9,7 @@ use tracing::{debug, trace, warn};
use crate::{
comp::{
inventory::{
item::{ItemDef, MaterialStatManifest},
item::{ItemDef, ItemKind, MaterialStatManifest},
loadout::Loadout,
slot::{EquipSlot, Slot, SlotError},
},
@ -676,6 +676,10 @@ impl Inventory {
true
}
pub fn equipped_items_of_kind(&self, item_kind: ItemKind) -> impl Iterator<Item = &Item> {
self.loadout.equipped_items_of_kind(item_kind)
}
}
impl Component for Inventory {

View File

@ -3,16 +3,15 @@ use super::{
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager},
util::loadout_slot_text,
Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_COMMON, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::{
hud::get_quality_col,
i18n::Localization,
ui::{
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, Tooltip, TooltipManager, Tooltipable,
ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
Tooltipable,
},
};
use client::Client;
@ -70,15 +69,14 @@ pub struct InventoryScroller<'a> {
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
show_stats: bool,
show_bag_inv: bool,
msm: &'a MaterialStatManifest,
on_right: bool,
item_tooltip: &'a Tooltip<'a>,
item_tooltip: &'a ItemTooltip<'a>,
playername: String,
is_us: bool,
inventory: &'a Inventory,
@ -92,15 +90,14 @@ impl<'a> InventoryScroller<'a> {
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
show_stats: bool,
show_bag_inv: bool,
msm: &'a MaterialStatManifest,
on_right: bool,
item_tooltip: &'a Tooltip<'a>,
item_tooltip: &'a ItemTooltip<'a>,
playername: String,
is_us: bool,
inventory: &'a Inventory,
@ -112,13 +109,12 @@ impl<'a> InventoryScroller<'a> {
item_imgs,
fonts,
common: widget::CommonBuilder::default(),
tooltip_manager,
item_tooltip_manager,
slot_manager,
pulse,
localized_strings,
show_stats,
show_bag_inv,
msm,
on_right,
item_tooltip,
playername,
@ -293,8 +289,6 @@ impl<'a> InventoryScroller<'a> {
}
if let Some(item) = item {
let (title, desc) = super::util::item_text(item, &self.msm);
let quality_col = get_quality_col(item);
let quality_col_img = match item.quality() {
Quality::Low => self.imgs.inv_slot_grey,
Quality::Common => self.imgs.inv_slot,
@ -305,19 +299,20 @@ impl<'a> InventoryScroller<'a> {
Quality::Artifact => self.imgs.inv_slot_orange,
_ => self.imgs.inv_slot_red,
};
let mut desc = desc.to_string();
let i18n = &self.localized_strings;
let mut prices_info: Option<String> = None;
if let Some((_, _, prices)) = self.client.pending_trade() {
super::util::append_price_desc(&mut desc, prices, item.item_definition_id());
prices_info = super::util::price_desc(prices, item.item_definition_id(), i18n);
}
slot_widget
.filled_slot(quality_col_img)
.with_tooltip(
self.tooltip_manager,
title,
&*desc,
.with_item_tooltip(
self.item_tooltip_manager,
item,
prices_info,
self.item_tooltip,
quality_col,
)
.set(state.ids.inv_slots[i], ui);
} else {
@ -451,6 +446,7 @@ pub struct Bag<'a> {
common: widget::CommonBuilder,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
@ -471,6 +467,7 @@ impl<'a> Bag<'a> {
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
@ -489,6 +486,7 @@ impl<'a> Bag<'a> {
common: widget::CommonBuilder::default(),
rot_imgs,
tooltip_manager,
item_tooltip_manager,
slot_manager,
pulse,
localized_strings,
@ -560,7 +558,7 @@ impl<'a> Widget for Bag<'a> {
};
// Tooltips
let item_tooltip = Tooltip::new({
let tooltip = Tooltip::new({
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
@ -578,18 +576,43 @@ impl<'a> Widget for Bag<'a> {
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
let item_tooltip = ItemTooltip::new(
{
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
},
self.client,
self.imgs,
self.item_imgs,
self.pulse,
self.msm,
self.localized_strings,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
InventoryScroller::new(
self.client,
self.imgs,
self.item_imgs,
self.fonts,
self.tooltip_manager,
self.item_tooltip_manager,
self.slot_manager,
self.pulse,
self.localized_strings,
self.show.stats,
self.show.bag_inv,
self.msm,
true,
&item_tooltip,
self.stats.name.to_string(),
@ -717,18 +740,16 @@ impl<'a> Widget for Bag<'a> {
} else {
btn.down_from(state.ids.stat_icons[i.0 - 1], 7.0)
};
// TODO: Translation
let tooltip_head = match i.1 {
"Health" => "Health",
"Stamina" => "Stamina",
"Combat Rating" => "Combat Rating",
"Protection" => "Protection",
"Health" => i18n.get("hud.bag.health"),
"Stamina" => i18n.get("hud.bag.stamina"),
"Combat Rating" => i18n.get("hud.bag.combat_rating"),
"Protection" => i18n.get("hud.bag.protection"),
_ => "",
};
// TODO: Translation
let tooltip_txt = match i.1 {
"Combat Rating" => "Calculated from your\nequipment and health.",
"Protection" => "Damage reduction through armor",
"Combat Rating" => i18n.get("hud.bag.combat_rating_desc"),
"Protection" => i18n.get("hud.bag.protection_desc"),
_ => "",
};
btn.with_tooltip(
@ -755,386 +776,369 @@ impl<'a> Widget for Bag<'a> {
}
// Loadout Slots
// Head
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Head)),
|| (i18n.get("hud.bag.head"), ""),
&self.msm,
);
let head_q_col = inventory
let head_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Head))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Head), [45.0; 2])
.mid_top_with_margin_on(state.bg_ids.bg_frame, 60.0)
.with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = head_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.head_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
head_q_col,
i18n.get("hud.bag.head"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.head_slot, ui);
.set(state.ids.head_slot, ui)
}
// Necklace
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Neck)),
|| (i18n.get("hud.bag.neck"), ""),
&self.msm,
);
let neck_q_col = inventory
let neck_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Neck))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Neck), [45.0; 2])
.mid_bottom_with_margin_on(state.ids.head_slot, -55.0)
.with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = neck_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.neck_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
neck_q_col,
i18n.get("hud.bag.neck"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.neck_slot, ui);
.set(state.ids.neck_slot, ui)
}
// Chest
//Image::new(self.imgs.armor_slot) // different graphics for empty/non empty
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Chest)),
|| (i18n.get("hud.bag.chest"), ""),
&self.msm,
);
let chest_q_col = inventory
let chest_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Chest))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Chest), [85.0; 2])
.mid_bottom_with_margin_on(state.ids.neck_slot, -95.0)
.with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = chest_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.chest_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
chest_q_col,
i18n.get("hud.bag.chest"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.chest_slot, ui);
.set(state.ids.chest_slot, ui)
}
// Shoulders
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Shoulders)),
|| (i18n.get("hud.bag.shoulders"), ""),
&self.msm,
);
let shoulder_q_col = inventory
let shoulder_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Shoulders))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Shoulders), [70.0; 2])
.bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
.with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = shoulder_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.shoulders_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
shoulder_q_col,
i18n.get("hud.bag.shoulders"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.shoulders_slot, ui);
.set(state.ids.shoulders_slot, ui)
}
// Hands
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Hands)),
|| (i18n.get("hud.bag.hands"), ""),
&self.msm,
);
let chest_q_col = inventory
let hands_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Hands))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Hands), [70.0; 2])
.bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
.with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = hands_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.hands_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
chest_q_col,
i18n.get("hud.bag.hands"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.hands_slot, ui);
.set(state.ids.hands_slot, ui)
}
// Belt
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Belt)),
|| (i18n.get("hud.bag.belt"), ""),
&self.msm,
);
let belt_q_col = inventory
let belt_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Belt))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Belt), [45.0; 2])
.mid_bottom_with_margin_on(state.ids.chest_slot, -55.0)
.with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = belt_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.belt_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
belt_q_col,
i18n.get("hud.bag.belt"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.belt_slot, ui);
.set(state.ids.belt_slot, ui)
}
// Legs
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Legs)),
|| (i18n.get("hud.bag.legs"), ""),
&self.msm,
);
let legs_q_col = inventory
let legs_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Legs))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Legs), [85.0; 2])
.mid_bottom_with_margin_on(state.ids.belt_slot, -95.0)
.with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = legs_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.legs_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
legs_q_col,
i18n.get("hud.bag.legs"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.legs_slot, ui);
.set(state.ids.legs_slot, ui)
}
// Ring
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Ring1)),
|| (i18n.get("hud.bag.ring"), ""),
&self.msm,
);
let ring_q_col = inventory
let ring1_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Ring1))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Ring1), [45.0; 2])
.bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0)
.with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = ring1_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.ring1_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
ring_q_col,
i18n.get("hud.bag.ring"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.ring1_slot, ui);
.set(state.ids.ring1_slot, ui)
}
// Ring 2
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Ring2)),
|| (i18n.get("hud.bag.ring"), ""),
&self.msm,
);
let ring2_q_col = inventory
let ring2_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Ring2))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Ring2), [45.0; 2])
.bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0)
.with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = ring2_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.ring2_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
ring2_q_col,
i18n.get("hud.bag.ring"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.ring2_slot, ui);
.set(state.ids.ring2_slot, ui)
}
// Back
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Back)),
|| (i18n.get("hud.bag.back"), ""),
&self.msm,
);
let back_q_col = inventory
let back_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Back))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Back), [45.0; 2])
.down_from(state.ids.ring2_slot, 10.0)
.with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = back_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.back_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
back_q_col,
i18n.get("hud.bag.back"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.back_slot, ui);
.set(state.ids.back_slot, ui)
}
// Foot
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Feet)),
|| (i18n.get("hud.bag.feet"), ""),
&self.msm,
);
let foot_q_col = inventory
let feet_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Feet))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Feet), [45.0; 2])
.down_from(state.ids.ring1_slot, 10.0)
.with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = feet_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.feet_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
foot_q_col,
i18n.get("hud.bag.feet"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.feet_slot, ui);
.set(state.ids.feet_slot, ui)
}
// Lantern
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Lantern),
|| (i18n.get("hud.bag.lantern"), ""),
&self.msm,
);
let lantern_q_col = inventory
let lantern_item = inventory
.equipped(EquipSlot::Lantern)
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Lantern, [45.0; 2])
.top_right_with_margins_on(state.bg_ids.bg_frame, 60.0, 5.0)
.with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = lantern_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.lantern_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
lantern_q_col,
i18n.get("hud.bag.lantern"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.lantern_slot, ui);
.set(state.ids.lantern_slot, ui)
}
// Glider
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Glider),
|| (i18n.get("hud.bag.glider"), ""),
&self.msm,
);
let glider_q_col = inventory
let glider_item = inventory
.equipped(EquipSlot::Glider)
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Glider, [45.0; 2])
.down_from(state.ids.lantern_slot, 5.0)
.with_icon(self.imgs.glider_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = glider_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.glider_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
glider_q_col,
i18n.get("hud.bag.glider"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.glider_slot, ui);
.set(state.ids.glider_slot, ui)
}
// Tabard
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Tabard)),
|| (i18n.get("hud.bag.tabard"), ""),
&self.msm,
);
let tabard_q_col = inventory
let tabard_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Tabard))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Tabard), [45.0; 2])
.down_from(state.ids.glider_slot, 5.0)
.with_icon(self.imgs.tabard_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = tabard_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.tabard_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
tabard_q_col,
i18n.get("hud.bag.tabard"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.tabard_slot, ui);
.set(state.ids.tabard_slot, ui)
}
// Mainhand/Left-Slot
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Mainhand),
|| (i18n.get("hud.bag.mainhand"), ""),
&self.msm,
);
let mainhand_q_col = inventory
let mainhand_item = inventory
.equipped(EquipSlot::Mainhand)
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Mainhand, [85.0; 2])
.bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0)
.with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = mainhand_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.mainhand_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
mainhand_q_col,
i18n.get("hud.bag.mainhand"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.mainhand_slot, ui);
.set(state.ids.mainhand_slot, ui)
}
// Offhand/Right-Slot
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Offhand),
|| (i18n.get("hud.bag.offhand"), ""),
&self.msm,
);
let offhand_q_col = inventory
let offhand_item = inventory
.equipped(EquipSlot::Offhand)
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Offhand, [85.0; 2])
.bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0)
.with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = offhand_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.offhand_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
offhand_q_col,
i18n.get("hud.bag.offhand"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.offhand_slot, ui);
.set(state.ids.offhand_slot, ui)
}
}
// Bag 1
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag1)),
|| (i18n.get("hud.bag.bag"), ""),
&self.msm,
);
let bag1_q_col = inventory
let bag1_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Bag1))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Bag1), [35.0; 2])
.bottom_left_with_margins_on(
state.bg_ids.bg_frame,
@ -1142,84 +1146,86 @@ impl<'a> Widget for Bag<'a> {
3.0,
)
.with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = bag1_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.bag1_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
bag1_q_col,
i18n.get("hud.bag.bag"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.bag1_slot, ui);
.set(state.ids.bag1_slot, ui)
}
// Bag 2
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag2)),
|| (i18n.get("hud.bag.bag"), ""),
&self.msm,
);
let bag2_q_col = inventory
let bag2_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Bag2))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Bag2), [35.0; 2])
.down_from(state.ids.bag1_slot, 2.0)
.with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = bag2_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.bag2_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
bag2_q_col,
i18n.get("hud.bag.bag"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.bag2_slot, ui);
.set(state.ids.bag2_slot, ui)
}
// Bag 3
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag3)),
|| (i18n.get("hud.bag.bag"), ""),
&self.msm,
);
let bag3_q_col = inventory
let bag3_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Bag3))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Bag3), [35.0; 2])
.down_from(state.ids.bag2_slot, 2.0)
.with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = bag3_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.bag3_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
bag3_q_col,
i18n.get("hud.bag.bag"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.bag3_slot, ui);
.set(state.ids.bag3_slot, ui)
}
// Bag 4
let (title, desc) = loadout_slot_text(
inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag4)),
|| (i18n.get("hud.bag.bag"), ""),
&self.msm,
);
let bag4_q_col = inventory
let bag4_item = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Bag4))
.map(|item| get_quality_col(item))
.unwrap_or(QUALITY_COMMON);
slot_maker
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Armor(ArmorSlot::Bag4), [35.0; 2])
.down_from(state.ids.bag3_slot, 2.0)
.with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
.filled_slot(filled_slot)
.with_tooltip(
.filled_slot(filled_slot);
if let Some(item) = bag4_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, None, &item_tooltip)
.set(state.ids.bag4_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
bag4_q_col,
i18n.get("hud.bag.bag"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.bag4_slot, ui);
.set(state.ids.bag4_slot, ui)
}
// Close button
if Button::image(self.imgs.close_btn)

View File

@ -4,9 +4,8 @@ use super::{
TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::{
hud::get_quality_col,
i18n::Localization,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
ui::{fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable},
};
use client::{self, Client};
use common::{
@ -65,7 +64,7 @@ pub struct Crafting<'a> {
localized_strings: &'a Localization,
pulse: f32,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
item_imgs: &'a ItemImgs,
inventory: &'a Inventory,
msm: &'a MaterialStatManifest,
@ -81,7 +80,7 @@ impl<'a> Crafting<'a> {
localized_strings: &'a Localization,
pulse: f32,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
item_imgs: &'a ItemImgs,
inventory: &'a Inventory,
msm: &'a MaterialStatManifest,
@ -93,7 +92,7 @@ impl<'a> Crafting<'a> {
localized_strings,
pulse,
rot_imgs,
tooltip_manager,
item_tooltip_manager,
item_imgs,
inventory,
msm,
@ -138,17 +137,27 @@ impl<'a> Widget for Crafting<'a> {
let mut events = Vec::new();
// Tooltips
let item_tooltip = Tooltip::new({
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
})
.title_font_size(self.fonts.cyri.scale(15))
let item_tooltip = ItemTooltip::new(
{
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
},
self.client,
self.imgs,
self.item_imgs,
self.pulse,
self.msm,
self.localized_strings,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
@ -300,8 +309,6 @@ impl<'a> Widget for Crafting<'a> {
{
let output_text = format!("x{}", &recipe.output.1.to_string());
// Output Image
let (title, desc) = super::util::item_text(&*recipe.output.0, self.msm);
let quality_col = get_quality_col(&*recipe.output.0);
Button::image(animate_by_pulse(
&self
.item_imgs
@ -316,12 +323,11 @@ impl<'a> Widget for Crafting<'a> {
.label_y(conrod_core::position::Relative::Scalar(-24.0))
.label_x(conrod_core::position::Relative::Scalar(24.0))
.middle_of(state.ids.output_img_frame)
.with_tooltip(
self.tooltip_manager,
title,
&*desc,
.with_item_tooltip(
self.item_tooltip_manager,
&*recipe.output.0,
None,
&item_tooltip,
quality_col,
)
.set(state.ids.output_img, ui);
}
@ -472,7 +478,6 @@ impl<'a> Widget for Crafting<'a> {
} else {
0.0
};
let quality_col = get_quality_col(&*item_def);
let quality_col_img = match &item_def.quality {
Quality::Low => self.imgs.inv_slot_grey,
Quality::Common => self.imgs.inv_slot,
@ -491,20 +496,13 @@ impl<'a> Widget for Crafting<'a> {
};
frame.set(state.ids.ingredient_frame[i], ui);
//Item Image
let (title, desc) = super::util::item_text(&*item_def, self.msm);
Button::image(animate_by_pulse(
&self.item_imgs.img_ids_or_not_found_img((&*item_def).into()),
self.pulse,
))
.w_h(22.0, 22.0)
.middle_of(state.ids.ingredient_frame[i])
.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
quality_col,
)
.with_item_tooltip(self.item_tooltip_manager, &*item_def, None, &item_tooltip)
.set(state.ids.ingredient_img[i], ui);
// Ingredients text and amount
// Don't show inventory amounts above 999 to avoid the widget clipping

View File

@ -7,8 +7,8 @@ mod diary;
mod esc_menu;
mod group;
mod hotbar;
mod img_ids;
mod item_imgs;
pub mod img_ids;
pub mod item_imgs;
mod map;
mod minimap;
mod overhead;
@ -20,7 +20,7 @@ mod skillbar;
mod slots;
mod social;
mod trade;
mod util;
pub mod util;
pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState};
pub use item_imgs::animate_by_pulse;
@ -263,6 +263,7 @@ widget_ids! {
crafting_window,
settings_window,
group_window,
item_info,
// Free look indicator
free_look_txt,
@ -903,8 +904,9 @@ impl Hud {
) -> Vec<Event> {
span!(_guard, "update_layout", "Hud::update_layout");
let mut events = std::mem::replace(&mut self.events, Vec::new());
let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets();
// pulse time for pulsating elements
let (ref mut ui_widgets, ref mut item_tooltip_manager, ref mut tooltip_manager) =
&mut self.ui.set_widgets();
// self.ui.set_item_widgets(); pulse time for pulsating elements
self.pulse = self.pulse + dt.as_secs_f32();
// FPS
let fps = global_state.clock.stats().average_tps;
@ -1338,6 +1340,7 @@ impl Hud {
&mut self.ids.overitems,
&mut ui_widgets.widget_id_generator(),
);
let ingame_pos = pos.0 + Vec3::unit_z() * 1.2;
let text = if item.amount() > 1 {
@ -1346,9 +1349,12 @@ impl Hud {
item.name().to_string()
};
let quality = get_quality_col(item);
// Item
overitem::Overitem::new(
&text,
&quality,
&distance,
&self.fonts,
&global_state.settings.controls,
@ -2226,6 +2232,7 @@ impl Hud {
controllers.get(entity).map(|c| &c.inputs),
) {
Skillbar::new(
client,
global_state,
&self.imgs,
&self.item_imgs,
@ -2239,6 +2246,7 @@ impl Hud {
//&controller,
&self.hotbar,
tooltip_manager,
item_tooltip_manager,
&mut self.slot_manager,
i18n,
&ability_map,
@ -2262,6 +2270,7 @@ impl Hud {
&self.fonts,
&self.rot_imgs,
tooltip_manager,
item_tooltip_manager,
&mut self.slot_manager,
self.pulse,
i18n,
@ -2298,7 +2307,7 @@ impl Hud {
&self.item_imgs,
&self.fonts,
&self.rot_imgs,
tooltip_manager,
item_tooltip_manager,
&mut self.slot_manager,
i18n,
&msm,
@ -2363,7 +2372,7 @@ impl Hud {
i18n,
self.pulse,
&self.rot_imgs,
tooltip_manager,
item_tooltip_manager,
&self.item_imgs,
&inventory,
&msm,
@ -3323,7 +3332,7 @@ impl Hud {
}
}
// Get item qualities of equipped items and assign a tooltip title/frame color
pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color {
pub fn get_quality_col<I: ItemDesc + ?Sized>(item: &I) -> Color {
match item.quality() {
Quality::Low => QUALITY_LOW,
Quality::Common => QUALITY_COMMON,

View File

@ -24,6 +24,7 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct Overitem<'a> {
name: &'a str,
quality: &'a Color,
distance_from_player_sqr: &'a f32,
fonts: &'a Fonts,
controls: &'a ControlSettings,
@ -34,12 +35,14 @@ pub struct Overitem<'a> {
impl<'a> Overitem<'a> {
pub fn new(
name: &'a str,
quality: &'a Color,
distance_from_player_sqr: &'a f32,
fonts: &'a Fonts,
controls: &'a ControlSettings,
) -> Self {
Self {
name,
quality,
distance_from_player_sqr,
fonts,
controls,
@ -118,7 +121,7 @@ impl<'a> Widget for Overitem<'a> {
Text::new(&self.name)
.font_id(self.fonts.cyri.conrod_id)
.font_size(text_font_size as u32)
.color(text_color)
.color(*self.quality)
.x_y(0.0, text_pos_y)
.depth(self.distance_from_player_sqr + 3.0)
.parent(id)

View File

@ -11,11 +11,14 @@ use crate::{
ui::{
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, Tooltip, TooltipManager, Tooltipable,
ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
Tooltipable,
},
window::GameInput,
GlobalState,
};
use client::{self, Client};
use common::comp::{
self,
inventory::slot::EquipSlot,
@ -130,6 +133,7 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct Skillbar<'a> {
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
@ -142,6 +146,7 @@ pub struct Skillbar<'a> {
// controller: &'a ControllerInputs,
hotbar: &'a hotbar::State,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a Localization,
pulse: f32,
@ -155,6 +160,7 @@ pub struct Skillbar<'a> {
impl<'a> Skillbar<'a> {
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn new(
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
@ -168,6 +174,7 @@ impl<'a> Skillbar<'a> {
// controller: &'a ControllerInputs,
hotbar: &'a hotbar::State,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a Localization,
ability_map: &'a AbilityMap,
@ -175,6 +182,7 @@ impl<'a> Skillbar<'a> {
combo: Option<ComboFloater>,
) -> Self {
Self {
client,
global_state,
imgs,
item_imgs,
@ -189,6 +197,7 @@ impl<'a> Skillbar<'a> {
// controller,
hotbar,
tooltip_manager,
item_tooltip_manager,
slot_manager,
localized_strings,
ability_map,
@ -464,7 +473,9 @@ impl<'a> Widget for Skillbar<'a> {
slot_manager: Some(self.slot_manager),
pulse: self.pulse,
};
let item_tooltip = Tooltip::new({
// Tooltips
let tooltip = Tooltip::new({
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
@ -481,6 +492,43 @@ impl<'a> Widget for Skillbar<'a> {
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
let item_tooltip = ItemTooltip::new(
{
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
},
self.client,
self.imgs,
self.item_imgs,
self.pulse,
self.msm,
self.localized_strings,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
let slot_content = |slot| {
content_source
.0
.get(slot)
.and_then(|content| match content {
hotbar::SlotContents::Inventory(i) => content_source.1.get(i),
_ => None,
})
};
// Helper
let tooltip_text = |slot| {
content_source
@ -542,8 +590,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::One, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.bottom_left_with_margins_on(state.ids.frame, 0.0, 0.0);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::One) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::One) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot1, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::One) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot1, ui);
} else {
slot.set(state.ids.slot1, ui);
@ -553,8 +604,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Two, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot1, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Two) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Two) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot2, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Two) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot2, ui);
} else {
slot.set(state.ids.slot2, ui);
@ -564,8 +618,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Three, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot2, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Three) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot3, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot3, ui);
} else {
slot.set(state.ids.slot3, ui);
@ -575,8 +632,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Four, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot3, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Four) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Four) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot4, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Four) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot4, ui);
} else {
slot.set(state.ids.slot4, ui);
@ -586,8 +646,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Five, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot4, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Five) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Five) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot5, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Five) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot5, ui);
} else {
slot.set(state.ids.slot5, ui);
@ -690,8 +753,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Six, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.m2_slot_bg, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Six) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Six) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot6, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Six) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot6, ui);
} else {
slot.set(state.ids.slot6, ui);
@ -701,8 +767,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Seven, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot6, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Seven) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Seven) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot7, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Seven) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot7, ui);
} else {
slot.set(state.ids.slot7, ui);
@ -712,8 +781,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Eight, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot7, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Eight) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Eight) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot8, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Eight) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot8, ui);
} else {
slot.set(state.ids.slot8, ui);
@ -723,8 +795,11 @@ impl<'a> Widget for Skillbar<'a> {
.fabricate(hotbar::Slot::Nine, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot8, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Nine) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
if let Some(item) = slot_content(hotbar::Slot::Nine) {
slot.with_item_tooltip(self.item_tooltip_manager, item, None, &item_tooltip)
.set(state.ids.slot9, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Nine) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot9, ui);
} else {
slot.set(state.ids.slot9, ui);
@ -737,7 +812,7 @@ impl<'a> Widget for Skillbar<'a> {
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot9, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Ten) {
slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR)
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot10, ui);
} else {
slot.set(state.ids.slot10, ui);

View File

@ -1,5 +1,4 @@
use super::{
get_quality_col,
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
slots::{SlotManager, TradeSlot},
@ -11,7 +10,7 @@ use crate::{
ui::{
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, Tooltip, TooltipManager, Tooltipable,
ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable,
},
};
use client::Client;
@ -63,7 +62,7 @@ pub struct Trade<'a> {
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
#[conrod(common_builder)]
common: widget::CommonBuilder,
slot_manager: &'a mut SlotManager,
@ -79,7 +78,7 @@ impl<'a> Trade<'a> {
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
localized_strings: &'a Localization,
msm: &'a MaterialStatManifest,
@ -91,7 +90,7 @@ impl<'a> Trade<'a> {
item_imgs,
fonts,
rot_imgs,
tooltip_manager,
item_tooltip_manager,
common: widget::CommonBuilder::default(),
slot_manager,
localized_strings,
@ -264,19 +263,28 @@ impl<'a> Trade<'a> {
prices: &'a Option<SitePrices>,
tradeslots: &[TradeSlot],
) {
let item_tooltip = Tooltip::new({
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
})
.title_font_size(self.fonts.cyri.scale(15))
// Tooltips
let item_tooltip = ItemTooltip::new(
{
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
},
self.client,
self.imgs,
self.item_imgs,
self.pulse,
self.msm,
self.localized_strings,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
@ -288,13 +296,12 @@ impl<'a> Trade<'a> {
self.imgs,
self.item_imgs,
self.fonts,
self.tooltip_manager,
self.item_tooltip_manager,
self.slot_manager,
self.pulse,
self.localized_strings,
false,
true,
self.msm,
false,
&item_tooltip,
name,
@ -354,8 +361,6 @@ impl<'a> Trade<'a> {
);
let slot_id = state.ids.inv_slots[i + who * MAX_TRADE_SLOTS];
if let Some(Some(item)) = slot.invslot.and_then(|slotid| inventory.slot(slotid)) {
let (title, desc) = super::util::item_text(item, self.msm);
let quality_col = get_quality_col(item);
let quality_col_img = match item.quality() {
Quality::Low => self.imgs.inv_slot_grey,
Quality::Common => self.imgs.inv_slot,
@ -366,17 +371,12 @@ impl<'a> Trade<'a> {
Quality::Artifact => self.imgs.inv_slot_orange,
_ => self.imgs.inv_slot_red,
};
let mut desc = desc.to_string();
super::util::append_price_desc(&mut desc, prices, item.item_definition_id());
let i18n = &self.localized_strings;
let prices_info = super::util::price_desc(prices, item.item_definition_id(), i18n);
slot_widget
.filled_slot(quality_col_img)
.with_tooltip(
self.tooltip_manager,
title,
&*desc,
&item_tooltip,
quality_col,
)
.with_item_tooltip(self.item_tooltip_manager, item, prices_info, &item_tooltip)
.set(slot_id, ui);
} else {
slot_widget.set(slot_id, ui);

View File

@ -4,7 +4,7 @@ use common::{
item::{
armor::{Armor, ArmorKind, Protection},
tool::{Hands, StatKind, Stats, Tool, ToolKind},
Item, ItemDesc, ItemKind, MaterialStatManifest, ModularComponent,
Item, ItemKind, MaterialStatManifest, ModularComponent,
},
BuffKind,
},
@ -13,75 +13,53 @@ use common::{
};
use std::{borrow::Cow, fmt::Write};
pub fn loadout_slot_text<'a>(
item: Option<&'a impl ItemDesc>,
mut empty: impl FnMut() -> (&'a str, &'a str),
msm: &'a MaterialStatManifest,
) -> (&'a str, Cow<'a, str>) {
item.map_or_else(
|| {
let (title, desc) = empty();
(title, Cow::Borrowed(desc))
},
|item| item_text(item, msm),
)
}
use crate::i18n::Localization;
pub fn item_text<'a>(
item: &'a impl ItemDesc,
msm: &'a MaterialStatManifest,
) -> (&'a str, Cow<'a, str>) {
let desc: Cow<str> = match item.kind() {
ItemKind::Armor(armor) => {
Cow::Owned(armor_desc(armor, item.description(), item.num_slots()))
},
ItemKind::Tool(tool) => Cow::Owned(tool_desc(
&tool,
item.components(),
&msm,
item.description(),
)),
ItemKind::ModularComponent(mc) => Cow::Owned(modular_component_desc(
mc,
item.components(),
&msm,
item.description(),
)),
ItemKind::Glider(_glider) => Cow::Owned(glider_desc(item.description())),
ItemKind::Consumable { effect, .. } => {
Cow::Owned(consumable_desc(effect, item.description()))
},
ItemKind::Throwable { .. } => Cow::Owned(throwable_desc(item.description())),
ItemKind::Utility { .. } => Cow::Owned(utility_desc(item.description())),
ItemKind::Ingredient { .. } => Cow::Owned(ingredient_desc(
item.description(),
item.item_definition_id(),
msm,
)),
ItemKind::Lantern { .. } => Cow::Owned(lantern_desc(item.description())),
ItemKind::TagExamples { .. } => Cow::Borrowed(item.description()),
//_ => Cow::Borrowed(item.description()),
};
(item.name(), desc)
}
pub fn append_price_desc(desc: &mut String, prices: &Option<SitePrices>, item_definition_id: &str) {
pub fn price_desc(
prices: &Option<SitePrices>,
item_definition_id: &str,
i18n: &Localization,
) -> Option<String> {
if let Some(prices) = prices {
let (material, factor) = TradePricing::get_material(item_definition_id);
let coinprice = prices.values.get(&Good::Coin).cloned().unwrap_or(1.0);
let buyprice = prices.values.get(&material).cloned().unwrap_or_default() * factor;
let sellprice = buyprice * material.trade_margin();
*desc += &format!(
"\n\nBuy price: {:0.1} coins\nSell price: {:0.1} coins",
Some(format!(
"{} : {:0.1} {}\n{} : {:0.1} {}",
i18n.get("hud.trade.buy_price"),
buyprice / coinprice,
sellprice / coinprice
);
i18n.get("hud.trade.coin"),
i18n.get("hud.trade.sell_price"),
sellprice / coinprice,
i18n.get("hud.trade.coin"),
))
} else {
None
}
}
// TODO: localization
fn modular_component_desc(
pub fn kind_text<'a>(kind: &ItemKind, i18n: &'a Localization) -> Cow<'a, str> {
match kind {
ItemKind::Armor(armor) => Cow::Borrowed(armor_kind(&armor, &i18n)),
ItemKind::Tool(tool) => Cow::Owned(format!(
"{} ({})",
tool_kind(&tool, i18n),
tool_hands(&tool, i18n)
)),
ItemKind::ModularComponent(_mc) => Cow::Borrowed(i18n.get("common.bag.shoulders")),
ItemKind::Glider(_glider) => Cow::Borrowed(i18n.get("common.kind.glider")),
ItemKind::Consumable { .. } => Cow::Borrowed(i18n.get("common.kind.consumable")),
ItemKind::Throwable { .. } => Cow::Borrowed(i18n.get("common.kind.throwable")),
ItemKind::Utility { .. } => Cow::Borrowed(i18n.get("common.kind.utility")),
ItemKind::Ingredient { .. } => Cow::Borrowed(i18n.get("common.kind.ingredient")),
ItemKind::Lantern { .. } => Cow::Borrowed(i18n.get("common.kind.lantern")),
ItemKind::TagExamples { .. } => Cow::Borrowed(""),
}
}
// TODO: localization, refactor when mc are player facing
pub fn modular_component_desc(
mc: &ModularComponent,
components: &[Item],
msm: &MaterialStatManifest,
@ -100,11 +78,9 @@ fn modular_component_desc(
}
result
}
fn glider_desc(desc: &str) -> String { format!("Glider\n\n{}\n\n<Right-Click to use>", desc) }
fn consumable_desc(effects: &[Effect], desc: &str) -> String {
// TODO: localization
let mut description = "Consumable".to_string();
pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String {
let mut description = String::new();
for effect in effects {
if let Effect::Buff(buff) = effect {
@ -113,34 +89,34 @@ fn consumable_desc(effects: &[Effect], desc: &str) -> String {
let str_total = dur_secs.map_or(strength, |secs| strength * secs);
let buff_desc = match buff.kind {
BuffKind::Saturation | BuffKind::Regeneration | BuffKind::Potion => {
format!("Restores {} Health", str_total)
},
BuffKind::IncreaseMaxEnergy => {
format!("Raises Maximum Stamina by {}", strength)
},
BuffKind::IncreaseMaxHealth => {
format!("Raises Maximum Health by {}", strength)
},
BuffKind::Invulnerability => "Grants invulnerability".to_string(),
BuffKind::Saturation | BuffKind::Regeneration | BuffKind::Potion => i18n
.get("buff.stat.health")
.replace("{str_total}", &str_total.to_string()),
BuffKind::IncreaseMaxEnergy => i18n
.get("buff.stat.increase_max_stamina")
.replace("{strength}", &strength.to_string()),
BuffKind::IncreaseMaxHealth => i18n
.get("buff.stat.increase_max_health")
.replace("{strength}", &strength.to_string()),
BuffKind::Invulnerability => i18n.get("buff.stat.invulenrability").to_string(),
BuffKind::Bleeding
| BuffKind::CampfireHeal
| BuffKind::Cursed
| BuffKind::ProtectingWard => continue,
};
write!(&mut description, "\n\n{}", buff_desc).unwrap();
write!(&mut description, "{}", buff_desc).unwrap();
let dur_desc = if dur_secs.is_some() {
let dur_desc = if let Some(dur_secs) = dur_secs {
match buff.kind {
BuffKind::Saturation | BuffKind::Regeneration => {
format!("over {} seconds", dur_secs.unwrap())
},
BuffKind::Saturation | BuffKind::Regeneration => i18n
.get("buff.text.over_seconds")
.replace("{dur_secs}", &dur_secs.to_string()),
BuffKind::IncreaseMaxEnergy
| BuffKind::IncreaseMaxHealth
| BuffKind::Invulnerability => {
format!("for {} seconds", dur_secs.unwrap())
},
| BuffKind::Invulnerability => i18n
.get("buff.text.for_seconds")
.replace("{dur_secs}", &dur_secs.to_string()),
BuffKind::Bleeding
| BuffKind::Potion
| BuffKind::CampfireHeal
@ -148,7 +124,7 @@ fn consumable_desc(effects: &[Effect], desc: &str) -> String {
| BuffKind::ProtectingWard => continue,
}
} else if let BuffKind::Saturation | BuffKind::Regeneration = buff.kind {
"every second".to_string()
i18n.get("buff.text.every_second").to_string()
} else {
continue;
};
@ -157,119 +133,61 @@ fn consumable_desc(effects: &[Effect], desc: &str) -> String {
}
}
if !desc.is_empty() {
write!(&mut description, "\n\n{}", desc).unwrap();
}
write!(&mut description, "\n\n<Right-Click to use>").unwrap();
description
}
fn throwable_desc(desc: &str) -> String {
format!("Can be thrown\n\n{}\n\n<Right-Click to use>", desc)
}
fn utility_desc(desc: &str) -> String { format!("{}\n\n<Right-Click to use>", desc) }
fn ingredient_desc(desc: &str, item_id: &str, msm: &MaterialStatManifest) -> String {
let mut result = format!("Crafting Ingredient\n\n{}", desc);
if let Some(stats) = msm.0.get(item_id) {
result += "\n\nStat multipliers:\n";
result += &statblock_desc(stats);
}
result
}
fn lantern_desc(desc: &str) -> String { format!("Lantern\n\n{}\n\n<Right-Click to use>", desc) }
fn armor_desc(armor: &Armor, desc: &str, slots: u16) -> String {
// TODO: localization
// Armor
fn armor_kind<'a>(armor: &Armor, i18n: &'a Localization) -> &'a str {
let kind = match armor.kind {
ArmorKind::Shoulder(_) => "Shoulders",
ArmorKind::Chest(_) => "Chest",
ArmorKind::Belt(_) => "Belt",
ArmorKind::Hand(_) => "Hands",
ArmorKind::Pants(_) => "Legs",
ArmorKind::Foot(_) => "Feet",
ArmorKind::Back(_) => "Back",
ArmorKind::Ring(_) => "Ring",
ArmorKind::Neck(_) => "Neck",
ArmorKind::Head(_) => "Head",
ArmorKind::Tabard(_) => "Tabard",
ArmorKind::Bag(_) => "Bag",
ArmorKind::Shoulder(_) => i18n.get("hud.bag.shoulders"),
ArmorKind::Chest(_) => i18n.get("hud.bag.chest"),
ArmorKind::Belt(_) => i18n.get("hud.bag.belt"),
ArmorKind::Hand(_) => i18n.get("hud.bag.hands"),
ArmorKind::Pants(_) => i18n.get("hud.bag.legs"),
ArmorKind::Foot(_) => i18n.get("hud.bag.feet"),
ArmorKind::Back(_) => i18n.get("hud.bag.back"),
ArmorKind::Ring(_) => i18n.get("hud.bag.ring"),
ArmorKind::Neck(_) => i18n.get("hud.bag.neck"),
ArmorKind::Head(_) => i18n.get("hud.bag.head"),
ArmorKind::Tabard(_) => i18n.get("hud.bag.tabard"),
ArmorKind::Bag(_) => i18n.get("hud.bag.bag"),
};
let armor_protection = match armor.get_protection() {
Protection::Normal(a) => a.to_string(),
Protection::Invincible => "Inf".to_string(),
};
let armor_poise_resilience = match armor.get_poise_resilience() {
Protection::Normal(a) => a.to_string(),
Protection::Invincible => "Inf".to_string(),
};
let mut description = format!(
"{}\n\nArmor: {}\n\nPoise Resilience: {}",
kind, armor_protection, armor_poise_resilience
);
if !desc.is_empty() {
write!(&mut description, "\n\n{}", desc).unwrap();
}
if slots > 0 {
write!(&mut description, "\n\nSlots: {}", slots).unwrap();
}
write!(&mut description, "\n\n<Right-Click to use>").unwrap();
description
kind
}
fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc: &str) -> String {
//Tool
fn tool_kind<'a>(tool: &Tool, i18n: &'a Localization) -> &'a str {
let kind = match tool.kind {
ToolKind::Sword => "Sword",
ToolKind::Axe => "Axe",
ToolKind::Hammer => "Hammer",
ToolKind::Bow => "Bow",
ToolKind::Dagger => "Dagger",
ToolKind::Staff => "Staff",
ToolKind::Sceptre => "Sceptre",
ToolKind::Shield => "Shield",
ToolKind::Spear => "Spear",
ToolKind::HammerSimple => "HammerSimple",
ToolKind::SwordSimple => "SwordSimple",
ToolKind::StaffSimple => "StaffSimple",
ToolKind::AxeSimple => "AxeSimple",
ToolKind::BowSimple => "BowSimple",
ToolKind::Unique(_) => "Unique",
ToolKind::Debug => "Debug",
ToolKind::Farming => "Farming Tool",
ToolKind::Pick => "Pickaxe",
ToolKind::Empty => "Empty",
ToolKind::Sword => i18n.get("common.weapons.sword"),
ToolKind::Axe => i18n.get("common.weapons.axe"),
ToolKind::Hammer => i18n.get("common.weapons.hammer"),
ToolKind::Bow => i18n.get("common.weapons.bow"),
ToolKind::Dagger => i18n.get("common.weapons.dagger"),
ToolKind::Staff => i18n.get("common.weapons.staff"),
ToolKind::Sceptre => i18n.get("common.weapons.sceptre"),
ToolKind::Shield => i18n.get("common.weapons.shield"),
ToolKind::Spear => i18n.get("common.weapons.spear"),
ToolKind::HammerSimple => i18n.get("common.weapons.hammer_simple"),
ToolKind::SwordSimple => i18n.get("common.weapons.sword_simple"),
ToolKind::StaffSimple => i18n.get("common.weapons.staff_simple"),
ToolKind::AxeSimple => i18n.get("common.weapons.axe_simple"),
ToolKind::BowSimple => i18n.get("common.weapons.bow_simple"),
ToolKind::Unique(_) => i18n.get("common.weapons.unique_simple"),
ToolKind::Debug => i18n.get("common.tool.debug"),
ToolKind::Farming => i18n.get("common.tool.farming"),
ToolKind::Pick => i18n.get("common.tool.pick"),
ToolKind::Empty => i18n.get("common.empty"),
};
kind
}
// Get tool stats
let stats = tool.stats.resolve_stats(msm, components).clamp_speed();
pub fn tool_hands<'a>(tool: &Tool, i18n: &'a Localization) -> &'a str {
let hands = match tool.hands {
Hands::One => "One",
Hands::Two => "Two",
Hands::One => i18n.get("common.hands.one"),
Hands::Two => i18n.get("common.hands.two"),
};
let mut result = format!("{}-Handed {}\n\n", hands, kind);
result += &statblock_desc(&stats);
if !components.is_empty() {
result += "Made from:\n";
for component in components {
result += component.name();
result += "\n"
}
result += "\n";
}
if !desc.is_empty() {
result += &format!("{}\n\n", desc);
}
result += "<Right-Click to use>";
result
hands
}
fn statblock_desc(stats: &Stats) -> String {
@ -285,87 +203,20 @@ fn statblock_desc(stats: &Stats) -> String {
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glider_desc() {
let item_description = "mushrooms";
assert_eq!(
"Glider\n\nmushrooms\n\n<Right-Click to use>",
glider_desc(item_description)
);
}
#[test]
fn test_consumable_desc() {
let item_description = "mushrooms";
assert_eq!(
"Consumable\n\nmushrooms\n\n<Right-Click to use>",
consumable_desc(&[], item_description)
);
}
#[test]
fn test_throwable_desc() {
let item_description = "mushrooms";
assert_eq!(
"Can be thrown\n\nmushrooms\n\n<Right-Click to use>",
throwable_desc(item_description)
);
}
#[test]
fn test_utility_desc() {
let item_description = "mushrooms";
assert_eq!(
"mushrooms\n\n<Right-Click to use>",
utility_desc(item_description)
);
}
#[test]
fn test_ingredient_desc() {
let mut testmsm = MaterialStatManifest(hashbrown::HashMap::new());
testmsm.0.insert(
"common.items.crafting_ing.bronze_ingot".to_string(),
Stats {
equip_time_secs: 0.0,
power: 3.0,
poise_strength: 5.0,
speed: 7.0,
crit_chance: 0.5,
crit_mult: 2.0,
},
);
assert_eq!(
"Crafting Ingredient\n\nmushrooms",
ingredient_desc("mushrooms", "common.items.food.mushroom", &testmsm)
);
assert_eq!(
"Crafting Ingredient\n\nA bronze ingot.\n\nStat multipliers:\nPower: 30.0\n\nPoise \
Strength: 50.0\n\nSpeed: 7.0\n\nCrit chance: 50.0%\n\nCrit damage: x2.0\n\n",
ingredient_desc(
"A bronze ingot.",
"common.items.crafting_ing.bronze_ingot",
&testmsm
)
);
}
#[test]
fn test_lantern_desc() {
let item_description = "mushrooms";
assert_eq!(
"Lantern\n\nmushrooms\n\n<Right-Click to use>",
lantern_desc(item_description)
);
// Compare two type, output a colored character to show comparison
pub fn comparison<T: PartialOrd>(first: T, other: T) -> (&'static str, conrod_core::Color) {
if first == other {
("", conrod_core::color::GREY)
} else if other < first {
("", conrod_core::color::GREEN)
} else {
("", conrod_core::color::RED)
}
}
pub fn protec2string(stat: Protection) -> String {
match stat {
Protection::Normal(a) => format!("{:.1}", a),
Protection::Invincible => "Inf".to_string(),
}
}

View File

@ -16,6 +16,7 @@ pub use widgets::{
image_frame::ImageFrame,
image_slider::ImageSlider,
ingame::{Ingame, Ingameable},
item_tooltip::{ItemTooltip, ItemTooltipManager, ItemTooltipable},
radio_list::RadioList,
slot,
toggle_button::ToggleButton,
@ -124,6 +125,8 @@ pub struct Ui {
scale: Scale,
// Tooltips
tooltip_manager: TooltipManager,
// Item tooltips manager
item_tooltip_manager: ItemTooltipManager,
}
impl Ui {
@ -138,6 +141,14 @@ impl Ui {
// to be updated, there's no reason to set the redraw count higher than
// 1.
ui.set_num_redraw_frames(1);
let item_tooltip_manager = ItemTooltipManager::new(
ui.widget_id_generator(),
Duration::from_millis(1),
Duration::from_millis(0),
scale.scale_factor_logical(),
);
let tooltip_manager = TooltipManager::new(
ui.widget_id_generator(),
Duration::from_millis(1),
@ -160,6 +171,7 @@ impl Ui {
need_cache_resize: false,
scale,
tooltip_manager,
item_tooltip_manager,
})
}
@ -223,8 +235,16 @@ impl Ui {
pub fn id_generator(&mut self) -> Generator { self.ui.widget_id_generator() }
pub fn set_widgets(&mut self) -> (UiCell, &mut TooltipManager) {
(self.ui.set_widgets(), &mut self.tooltip_manager)
pub fn set_widgets(&mut self) -> (UiCell, &mut ItemTooltipManager, &mut TooltipManager) {
(
self.ui.set_widgets(),
&mut self.item_tooltip_manager,
&mut self.tooltip_manager,
)
}
pub fn set_item_widgets(&mut self) -> (UiCell, &mut ItemTooltipManager) {
(self.ui.set_widgets(), &mut self.item_tooltip_manager)
}
// Accepts Option so widget can be unfocused.
@ -292,6 +312,10 @@ impl Ui {
self.tooltip_manager
.maintain(self.ui.global_input(), self.scale.scale_factor_logical());
// Maintain tooltip manager
self.item_tooltip_manager
.maintain(self.ui.global_input(), self.scale.scale_factor_logical());
// Handle scale factor changing
let need_resize = if let Some(scale_factor) = self.scale_factor_changed.take() {
self.scale.scale_factor_changed(scale_factor)

View File

@ -0,0 +1,990 @@
use super::image_frame::ImageFrame;
use crate::{
hud::{
get_quality_col,
img_ids::Imgs,
item_imgs::{animate_by_pulse, ItemImgs, ItemKey},
util,
},
i18n::Localization,
};
use client::Client;
use common::comp::item::{
armor::Protection, Item, ItemDesc, ItemKind, MaterialStatManifest, Quality,
};
use conrod_core::{
builder_method, builder_methods, image, input::global::Global, position::Dimension, text,
widget, widget_ids, Color, Colorable, FontSize, Positionable, Scalar, Sizeable, Ui, UiCell,
Widget, WidgetCommon, WidgetStyle,
};
use lazy_static::lazy_static;
use std::time::{Duration, Instant};
#[derive(Copy, Clone)]
struct Hover(widget::Id, [f64; 2]);
#[derive(Copy, Clone)]
enum HoverState {
Hovering(Hover),
Fading(Instant, Hover, Option<(Instant, widget::Id)>),
Start(Instant, widget::Id),
None,
}
// Spacing between the tooltip and mouse
const MOUSE_PAD_Y: f64 = 15.0;
pub struct ItemTooltipManager {
tooltip_id: widget::Id,
state: HoverState,
// How long before a tooltip is displayed when hovering
hover_dur: Duration,
// How long it takes a tooltip to disappear
fade_dur: Duration,
// Current scaling of the ui
logical_scale_factor: f64,
}
impl ItemTooltipManager {
pub fn new(
mut generator: widget::id::Generator,
hover_dur: Duration,
fade_dur: Duration,
logical_scale_factor: f64,
) -> Self {
Self {
tooltip_id: generator.next(),
state: HoverState::None,
hover_dur,
fade_dur,
logical_scale_factor,
}
}
pub fn maintain(&mut self, input: &Global, logical_scale_factor: f64) {
self.logical_scale_factor = logical_scale_factor;
let current = &input.current;
if let Some(um_id) = current.widget_under_mouse {
match self.state {
HoverState::Hovering(hover) if um_id == hover.0 => (),
HoverState::Hovering(hover) => {
self.state =
HoverState::Fading(Instant::now(), hover, Some((Instant::now(), um_id)))
},
HoverState::Fading(_, _, Some((_, id))) if um_id == id => {},
HoverState::Fading(start, hover, _) => {
self.state = HoverState::Fading(start, hover, Some((Instant::now(), um_id)))
},
HoverState::Start(_, id) if um_id == id => (),
HoverState::Start(_, _) | HoverState::None => {
self.state = HoverState::Start(Instant::now(), um_id)
},
}
} else {
match self.state {
HoverState::Hovering(hover) => {
self.state = HoverState::Fading(Instant::now(), hover, None)
},
HoverState::Fading(start, hover, Some((_, _))) => {
self.state = HoverState::Fading(start, hover, None)
},
HoverState::Start(_, _) => self.state = HoverState::None,
HoverState::Fading(_, _, None) | HoverState::None => (),
}
}
// Handle fade timing
if let HoverState::Fading(start, _, maybe_hover) = self.state {
if start.elapsed() > self.fade_dur {
self.state = match maybe_hover {
Some((start, hover)) => HoverState::Start(start, hover),
None => HoverState::None,
};
}
}
}
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
fn set_tooltip(
&mut self,
tooltip: &ItemTooltip,
item: &dyn ItemDesc,
extra: Option<String>,
img_id: Option<image::Id>,
image_dims: Option<(f64, f64)>,
src_id: widget::Id,
ui: &mut UiCell,
) {
let tooltip_id = self.tooltip_id;
let mp_h = MOUSE_PAD_Y / self.logical_scale_factor;
let tooltip = |transparency, mouse_pos: [f64; 2], ui: &mut UiCell| {
// Fill in text and the potential image beforehand to get an accurate size for
// spacing
let tooltip = tooltip
.clone()
.item(item)
.extra(extra)
.image(img_id)
.image_dims(image_dims);
let [t_w, t_h] = tooltip.get_wh(ui).unwrap_or([0.0, 0.0]);
let [m_x, m_y] = [mouse_pos[0], mouse_pos[1]];
let (w_w, w_h) = (ui.win_w, ui.win_h);
// Determine position based on size and mouse position
// Flow to the top left of the mouse when there is space
let x = if (m_x + w_w / 2.0) > t_w {
m_x - t_w / 2.0
} else {
m_x + t_w / 2.0
};
let y = if w_h - (m_y + w_h / 2.0) > t_h + mp_h {
m_y + mp_h + t_h / 2.0
} else {
m_y - mp_h - t_h / 2.0
};
tooltip
.floating(true)
.transparency(transparency)
.x_y(x, y)
.set(tooltip_id, ui);
};
match self.state {
HoverState::Hovering(Hover(id, xy)) if id == src_id => tooltip(1.0, xy, ui),
HoverState::Fading(start, Hover(id, xy), _) if id == src_id => tooltip(
(0.1f32 - start.elapsed().as_millis() as f32 / self.hover_dur.as_millis() as f32)
.max(0.0),
xy,
ui,
),
HoverState::Start(start, id) if id == src_id && start.elapsed() > self.hover_dur => {
let xy = ui.global_input().current.mouse.xy;
self.state = HoverState::Hovering(Hover(id, xy));
tooltip(1.0, xy, ui);
},
_ => (),
}
}
}
pub struct ItemTooltipped<'a, W> {
inner: W,
tooltip_manager: &'a mut ItemTooltipManager,
item: &'a dyn ItemDesc,
extra: Option<String>,
img_id: Option<image::Id>,
image_dims: Option<(f64, f64)>,
tooltip: &'a ItemTooltip<'a>,
}
impl<'a, W: Widget> ItemTooltipped<'a, W> {
pub fn tooltip_image(mut self, img_id: image::Id) -> Self {
self.img_id = Some(img_id);
self
}
pub fn tooltip_image_dims(mut self, dims: (f64, f64)) -> Self {
self.image_dims = Some(dims);
self
}
pub fn set(self, id: widget::Id, ui: &mut UiCell) -> W::Event {
let event = self.inner.set(id, ui);
self.tooltip_manager.set_tooltip(
self.tooltip,
self.item,
self.extra,
self.img_id,
self.image_dims,
id,
ui,
);
event
}
}
pub trait ItemTooltipable {
// If `Tooltip` is expensive to construct accept a closure here instead.
fn with_item_tooltip<'a>(
self,
tooltip_manager: &'a mut ItemTooltipManager,
item: &'a dyn ItemDesc,
extra: Option<String>,
tooltip: &'a ItemTooltip<'a>,
) -> ItemTooltipped<'a, Self>
where
Self: std::marker::Sized;
}
impl<W: Widget> ItemTooltipable for W {
fn with_item_tooltip<'a>(
self,
tooltip_manager: &'a mut ItemTooltipManager,
item: &'a dyn ItemDesc,
extra: Option<String>,
tooltip: &'a ItemTooltip<'a>,
) -> ItemTooltipped<'a, W> {
ItemTooltipped {
inner: self,
tooltip_manager,
item,
extra,
img_id: None,
image_dims: None,
tooltip,
}
}
}
/// Vertical spacing between elements of the tooltip
const V_PAD: f64 = 10.0;
/// Horizontal spacing between elements of the tooltip
const H_PAD: f64 = 10.0;
/// Vertical spacing between stats
const V_PAD_STATS: f64 = 6.0;
/// Default portion of inner width that goes to an image
const IMAGE_W_FRAC: f64 = 0.3;
// Item icon size
const ICON_SIZE: [f64; 2] = [64.0, 64.0];
/// A widget for displaying tooltips
#[derive(Clone, WidgetCommon)]
pub struct ItemTooltip<'a> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
item: &'a dyn ItemDesc,
msm: &'a MaterialStatManifest,
extra: Option<String>,
image: Option<image::Id>,
image_dims: Option<(f64, f64)>,
style: Style,
transparency: f32,
image_frame: ImageFrame,
client: &'a Client,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
pulse: f32,
localized_strings: &'a Localization,
}
#[derive(Clone, Debug, Default, PartialEq, WidgetStyle)]
pub struct Style {
#[conrod(default = "Color::Rgba(1.0, 1.0, 1.0, 1.0)")]
pub color: Option<Color>,
title: widget::text::Style,
desc: widget::text::Style,
// add background imgs here
}
widget_ids! {
struct Ids {
title,
subtitle,
desc,
extra,
main_stat,
main_stat_text,
stats[],
diff_main_stat,
diffs[],
item_frame,
item_render,
image_frame,
image,
background,
}
}
pub struct State {
ids: Ids,
}
lazy_static! {
static ref EMPTY_ITEM: Item = Item::new_from_asset_expect("common.items.weapons.empty.empty");
}
impl<'a> ItemTooltip<'a> {
builder_methods! {
pub desc_text_color { style.desc.color = Some(Color) }
pub title_font_size { style.title.font_size = Some(FontSize) }
pub desc_font_size { style.desc.font_size = Some(FontSize) }
pub title_justify { style.title.justify = Some(text::Justify) }
pub desc_justify { style.desc.justify = Some(text::Justify) }
pub title_line_spacing { style.title.line_spacing = Some(Scalar) }
pub desc_line_spacing { style.desc.line_spacing = Some(Scalar) }
image { image = Option<image::Id> }
item { item = &'a dyn ItemDesc }
extra { extra = Option<String> }
msm { msm = &'a MaterialStatManifest }
image_dims { image_dims = Option<(f64, f64)> }
transparency { transparency = f32 }
}
pub fn new(
image_frame: ImageFrame,
client: &'a Client,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
pulse: f32,
msm: &'a MaterialStatManifest,
localized_strings: &'a Localization,
) -> Self {
ItemTooltip {
common: widget::CommonBuilder::default(),
style: Style::default(),
item: &*EMPTY_ITEM,
msm,
extra: None,
transparency: 1.0,
image_frame,
image: None,
image_dims: None,
client: &client,
imgs: &imgs,
item_imgs: &item_imgs,
pulse,
localized_strings,
}
}
/// Align the text to the left of its bounding **Rect**'s *x* axis range.
//pub fn left_justify(self) -> Self {
// self.justify(text::Justify::Left)
//}
/// Align the text to the middle of its bounding **Rect**'s *x* axis range.
//pub fn center_justify(self) -> Self {
// self.justify(text::Justify::Center)
//}
/// Align the text to the right of its bounding **Rect**'s *x* axis range.
//pub fn right_justify(self) -> Self {
// self.justify(text::Justify::Right)
//}
fn text_image_width(&self, total_width: f64) -> (f64, f64) {
let inner_width = (total_width - H_PAD * 2.0).max(0.0);
// Image defaults to 30% of the width
let image_w = if self.image.is_some() {
match self.image_dims {
Some((w, _)) => w,
None => (inner_width - H_PAD).max(0.0) * IMAGE_W_FRAC,
}
} else {
0.0
};
// Text gets the remaining width
let text_w = (inner_width
- if self.image.is_some() {
image_w + H_PAD
} else {
0.0
})
.max(0.0);
(text_w, image_w)
}
/// Specify the font used for displaying the text.
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
self.style.title.font_id = Some(Some(font_id));
self.style.desc.font_id = Some(Some(font_id));
self
}
}
impl<'a> Widget for ItemTooltip<'a> {
type Event = ();
type State = State;
type Style = Style;
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style { self.style.clone() }
fn update(self, args: widget::UpdateArgs<Self>) {
let widget::UpdateArgs {
id,
state,
rect,
ui,
..
} = args;
fn stats_count(item: &dyn ItemDesc) -> usize {
let mut count = match item.kind() {
ItemKind::Armor(_) => 1,
ItemKind::Tool(_) => 5,
ItemKind::Consumable { .. } => 1,
_ => 0,
};
if item.num_slots() != 0 {
count += 1
}
count as usize
}
let i18n = &self.localized_strings;
let inventories = self.client.inventories();
let inventory = match inventories.get(self.client.entity()) {
Some(l) => l,
None => return,
};
let item = self.item;
let quality = get_quality_col(item);
let equip_slot = inventory.equipped_items_of_kind(item.kind().clone());
let (title, desc) = (item.name().to_string(), item.description().to_string());
let subtitle = util::kind_text(item.kind(), i18n);
let text_color = conrod_core::color::WHITE;
// Widths
let (text_w, image_w) = self.text_image_width(rect.w());
// Color quality
let quality_col_img = match &item.quality() {
Quality::Low => self.imgs.inv_slot_grey,
Quality::Common => self.imgs.inv_slot,
Quality::Moderate => self.imgs.inv_slot_green,
Quality::High => self.imgs.inv_slot_blue,
Quality::Epic => self.imgs.inv_slot_purple,
Quality::Legendary => self.imgs.inv_slot_gold,
Quality::Artifact => self.imgs.inv_slot_orange,
_ => self.imgs.inv_slot_red,
};
// Update windget array size
state.update(|s| {
s.ids
.stats
.resize(stats_count(item), &mut ui.widget_id_generator())
});
state.update(|s| {
s.ids
.diffs
.resize(stats_count(item), &mut ui.widget_id_generator())
});
// Background image frame
self.image_frame
.wh(rect.dim())
.xy(rect.xy())
.graphics_for(id)
.parent(id)
.color(quality)
.set(state.ids.image_frame, ui);
// Image
if let Some(img_id) = self.image {
widget::Image::new(img_id)
.w_h(image_w, self.image_dims.map_or(image_w, |(_, h)| h))
.graphics_for(id)
.parent(id)
.color(Some(quality))
.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
.set(state.ids.image, ui);
}
// Title
widget::Text::new(&title)
.w(text_w)
.graphics_for(id)
.parent(id)
.with_style(self.style.title)
.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
.center_justify()
.color(quality)
.set(state.ids.title, ui);
// Item frame
widget::Image::new(quality_col_img)
.wh(ICON_SIZE)
.graphics_for(id)
.parent(id)
.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
.down_from(state.ids.title, V_PAD)
.set(state.ids.item_frame, ui);
// Item render
widget::Image::new(animate_by_pulse(
&self
.item_imgs
.img_ids_or_not_found_img(ItemKey::from(&item)),
self.pulse,
))
.color(Some(conrod_core::color::WHITE))
.w_h(ICON_SIZE[0] * 0.8, ICON_SIZE[1] * 0.8)
.middle_of(state.ids.item_frame)
.set(state.ids.item_render, ui);
// Subtitle
widget::Text::new(&subtitle)
.w(text_w)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(conrod_core::color::GREY)
.right_from(state.ids.item_frame, H_PAD)
.set(state.ids.subtitle, ui);
// Stats
match item.kind() {
ItemKind::Tool(tool) => {
let power = tool.base_power(self.msm, item.components()) * 10.0;
let speed = tool.base_speed(self.msm, item.components());
let poise_str = tool.base_poise_strength(self.msm, item.components()) * 10.0;
let crit_chance = tool.base_crit_chance(self.msm, item.components()) * 100.0;
let crit_mult = tool.base_crit_mult(self.msm, item.components());
let dps = power * speed;
// DPS
widget::Text::new(&format!("{:.1}", dps))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.font_size(34)
.align_middle_y_of(state.ids.item_frame)
.right_from(state.ids.item_frame, H_PAD)
.set(state.ids.main_stat, ui);
widget::Text::new(i18n.get("common.stats.dps"))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.align_bottom_of(state.ids.main_stat)
.right_from(state.ids.main_stat, H_PAD)
.set(state.ids.main_stat_text, ui);
// Power
widget::Text::new(&format!(
"{} : {:.1}",
i18n.get("common.stats.power"),
power
))
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.item_frame, V_PAD)
.set(state.ids.stats[0], ui);
// Speed
widget::Text::new(&format!(
"{} : {:.1}",
i18n.get("common.stats.speed"),
speed
))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.stats[0], V_PAD_STATS)
.set(state.ids.stats[1], ui);
// Poise
widget::Text::new(&format!(
"{} : {:.1}",
i18n.get("common.stats.poise"),
poise_str
))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.stats[1], V_PAD_STATS)
.set(state.ids.stats[2], ui);
// Crit chance
widget::Text::new(&format!(
"{} : {:.1}%",
i18n.get("common.stats.crit_chance"),
crit_chance
))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.stats[2], V_PAD_STATS)
.set(state.ids.stats[3], ui);
// Crit mult
widget::Text::new(&format!(
"{} : x{:.1}",
i18n.get("common.stats.crit_mult"),
crit_mult
))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.stats[3], V_PAD_STATS)
.set(state.ids.stats[4], ui);
if let Some(equipped_item) = equip_slot.cloned().next() {
if let ItemKind::Tool(equipped_tool) = equipped_item.kind() {
let tool_stats = tool
.stats
.resolve_stats(self.msm, item.components())
.clamp_speed();
let equipped_tool_stats = equipped_tool
.stats
.resolve_stats(self.msm, equipped_item.components())
.clamp_speed();
let diff = tool_stats - equipped_tool_stats;
let power_diff =
util::comparison(tool_stats.power, equipped_tool_stats.power);
let speed_diff =
util::comparison(tool_stats.speed, equipped_tool_stats.speed);
let poise_strength_diff = util::comparison(
tool_stats.poise_strength,
equipped_tool_stats.poise_strength,
);
let crit_chance_diff = util::comparison(
tool_stats.crit_chance,
equipped_tool_stats.crit_chance,
);
let crit_mult_diff =
util::comparison(tool_stats.crit_mult, equipped_tool_stats.crit_mult);
let equipped_dps =
equipped_tool_stats.power * equipped_tool_stats.speed * 10.0;
let diff_main_stat = util::comparison(dps, equipped_dps);
if equipped_dps - dps != 0.0 {
widget::Text::new(&diff_main_stat.0)
.right_from(state.ids.main_stat_text, H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(diff_main_stat.1)
.set(state.ids.diff_main_stat, ui);
}
if diff.power != 0.0 {
widget::Text::new(&format!(
"{} {:.1}",
&power_diff.0,
&diff.power * 10.0
))
.align_middle_y_of(state.ids.stats[0])
.right_from(state.ids.stats[0], H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(power_diff.1)
.set(state.ids.diffs[0], ui);
}
if diff.speed != 0.0 {
widget::Text::new(&format!("{} {:.1}", &speed_diff.0, &diff.speed))
.align_middle_y_of(state.ids.stats[1])
.right_from(state.ids.stats[1], H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(speed_diff.1)
.set(state.ids.diffs[1], ui);
}
if diff.poise_strength != 0.0 {
widget::Text::new(&format!(
"{} {:.1}",
&poise_strength_diff.0,
&diff.poise_strength * 10.0
))
.align_middle_y_of(state.ids.stats[2])
.right_from(state.ids.stats[2], H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(poise_strength_diff.1)
.set(state.ids.diffs[2], ui);
}
if diff.crit_chance != 0.0 {
widget::Text::new(&format!(
"{} {:.1}%",
&crit_chance_diff.0,
&diff.crit_chance * 100.0
))
.align_middle_y_of(state.ids.stats[3])
.right_from(state.ids.stats[3], H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(crit_chance_diff.1)
.set(state.ids.diffs[3], ui);
}
if diff.crit_mult != 0.0 {
widget::Text::new(&format!(
"{} {:.1}",
&crit_mult_diff.0, &diff.crit_mult
))
.align_middle_y_of(state.ids.stats[4])
.right_from(state.ids.stats[4], H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(crit_mult_diff.1)
.set(state.ids.diffs[4], ui);
}
}
}
},
ItemKind::Armor(armor) => {
let protection = armor.get_protection();
let poise_res = armor.get_poise_resilience();
// Armour
widget::Text::new(&util::protec2string(protection))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.font_size(34)
.align_middle_y_of(state.ids.item_frame)
.right_from(state.ids.item_frame, H_PAD)
.set(state.ids.main_stat, ui);
widget::Text::new(i18n.get("common.stats.armor"))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.align_bottom_of(state.ids.main_stat)
.right_from(state.ids.main_stat, H_PAD)
.set(state.ids.main_stat_text, ui);
// Poise res
widget::Text::new(&format!(
"{} : {}",
i18n.get("common.stats.poise_res"),
util::protec2string(poise_res)
))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.down_from(state.ids.item_frame, V_PAD)
.set(state.ids.stats[0], ui);
// Slots
if item.num_slots() > 0 {
widget::Text::new(&format!(
"{} : {}",
i18n.get("common.stats.slots"),
item.num_slots()
))
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.down_from(state.ids.stats[0], V_PAD_STATS)
.set(state.ids.stats[1], ui);
}
if let Some(equipped_item) = equip_slot.cloned().next() {
if let ItemKind::Armor(equipped_armor) = equipped_item.kind() {
let diff = armor.stats - equipped_armor.stats;
let protection_diff = util::comparison(
&armor.get_protection(),
&equipped_armor.get_protection(),
);
let poise_res_diff = util::comparison(
&armor.get_poise_resilience(),
&equipped_armor.get_poise_resilience(),
);
if diff.get_protection() != Protection::Normal(0.0) {
widget::Text::new(&protection_diff.0)
.right_from(state.ids.main_stat_text, H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(protection_diff.1)
.set(state.ids.diff_main_stat, ui);
}
if diff.get_poise_resilience() != Protection::Normal(0.0) {
widget::Text::new(&format!(
"{} {}",
&poise_res_diff.0,
util::protec2string(diff.get_poise_resilience())
))
.align_middle_y_of(state.ids.stats[0])
.right_from(state.ids.stats[0], H_PAD)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(poise_res_diff.1)
.set(state.ids.diffs[0], ui);
}
}
}
},
ItemKind::Consumable { effect, .. } => {
widget::Text::new(&util::consumable_desc(effect, i18n))
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.item_frame, V_PAD)
.set(state.ids.stats[0], ui);
},
ItemKind::ModularComponent(mc) => {
widget::Text::new(&util::modular_component_desc(
mc,
item.components(),
&self.msm,
item.description(),
))
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(text_color)
.down_from(state.ids.item_frame, V_PAD)
.set(state.ids.stats[0], ui);
},
_ => (),
}
// Description
if !desc.is_empty() {
widget::Text::new(&format!("\"{}\"", &desc))
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.color(conrod_core::color::GREY)
.down_from(
if stats_count(item) > 0 {
state.ids.stats[state.ids.stats.len() - 1]
} else {
state.ids.item_frame
},
V_PAD,
)
.w(text_w)
.set(state.ids.desc, ui);
}
// Extra text
if let Some(extra) = self.extra {
widget::Text::new(&extra)
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
.graphics_for(id)
.parent(id)
.with_style(self.style.desc)
.down_from(
if !desc.is_empty() {
state.ids.desc
} else if stats_count(item) > 0 {
state.ids.stats[state.ids.stats.len() - 1]
} else {
state.ids.item_frame
},
V_PAD,
)
.w(text_w)
.set(state.ids.extra, ui);
}
}
/// Default width is based on the description font size unless the text is
/// small enough to fit on a single line
fn default_x_dimension(&self, _ui: &Ui) -> Dimension { Dimension::Absolute(260.0) }
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
fn stats_count(item: &dyn ItemDesc) -> usize {
let mut count = match item.kind() {
ItemKind::Armor(_) => 1,
ItemKind::Tool(_) => 5,
ItemKind::Consumable { .. } => 1,
ItemKind::ModularComponent { .. } => 1,
_ => 0,
};
if item.num_slots() != 0 {
count += 1
}
count as usize
}
let item = &self.item;
let (title, desc) = (item.name().to_string(), item.description().to_string());
let (text_w, _image_w) = self.text_image_width(260.0);
// Title
let title_h = widget::Text::new(&title)
.w(text_w)
.with_style(self.style.title)
.get_h(ui)
.unwrap_or(0.0)
+ V_PAD;
// Item frame
let frame_h = ICON_SIZE[1] + V_PAD;
// Stats
let stat_h = if stats_count(self.item) > 0 {
widget::Text::new(&"placeholder".to_string())
.with_style(self.style.desc)
.get_h(ui)
.unwrap_or(0.0)
* stats_count(self.item) as f64
+ (stats_count(self.item) - 1) as f64 * V_PAD_STATS
+ V_PAD
} else {
0.0
};
// Description
let desc_h: f64 = if !desc.is_empty() {
widget::Text::new(&format!("\"{}\"", &desc))
.with_style(self.style.desc)
.w(text_w)
.get_h(ui)
.unwrap_or(0.0)
+ V_PAD
} else {
0.0
};
// Price
let price_h: f64 = if let Some(extra) = &self.extra {
widget::Text::new(&extra)
.with_style(self.style.desc)
.w(text_w)
.get_h(ui)
.unwrap_or(0.0)
+ V_PAD
} else {
0.0
};
let height = title_h + frame_h + stat_h + desc_h + price_h + V_PAD + 5.0; // extra padding to fit frame top padding
Dimension::Absolute(height)
}
}
impl<'a> Colorable for ItemTooltip<'a> {
builder_method!(color { style.color = Some(Color) });
}

View File

@ -2,6 +2,7 @@ pub mod ghost_image;
pub mod image_frame;
pub mod image_slider;
pub mod ingame;
pub mod item_tooltip;
pub mod radio_list;
pub mod slot;
pub mod toggle_button;