mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
New tooltip
This commit is contained in:
parent
88cdd24847
commit
4786f84afd
@ -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 {
|
||||
@ -26,12 +27,12 @@ impl Armor {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Stats {
|
||||
protection: Protection,
|
||||
poise_resilience: Protection,
|
||||
pub struct Stats<Protection> {
|
||||
pub protection: Protection,
|
||||
pub poise_resilience: Protection,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
impl Stats<Protection> {
|
||||
// DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
// Added for csv import of stats
|
||||
pub fn new(protection: Protection, poise_resilience: Protection) -> Self {
|
||||
@ -42,16 +43,52 @@ impl Stats {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Stats<Protection>> for Stats<Protection> {
|
||||
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)]
|
||||
pub enum Protection {
|
||||
Invincible,
|
||||
Normal(f32),
|
||||
}
|
||||
|
||||
impl Sub<Protection> 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::Less),
|
||||
(_, Protection::Invincible) => Some(Ordering::Greater),
|
||||
(Protection::Normal(a), Protection::Normal(b)) => f32::partial_cmp(&a, &b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Armor {
|
||||
pub kind: ArmorKind,
|
||||
pub stats: Stats,
|
||||
pub stats: Stats<Protection>,
|
||||
}
|
||||
|
||||
impl Armor {
|
||||
|
@ -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>);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
ui::{
|
||||
fonts::Fonts,
|
||||
slot::{ContentSize, SlotMaker},
|
||||
ImageFrame, Tooltip, TooltipManager, Tooltipable,
|
||||
ImageFrame, Tooltip, TooltipManager, Tooltipable, ItemTooltip, ItemTooltipManager, ItemTooltipable,
|
||||
},
|
||||
};
|
||||
use client::Client;
|
||||
@ -451,6 +451,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 +472,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 +491,7 @@ impl<'a> Bag<'a> {
|
||||
common: widget::CommonBuilder::default(),
|
||||
rot_imgs,
|
||||
tooltip_manager,
|
||||
item_tooltip_manager,
|
||||
slot_manager,
|
||||
pulse,
|
||||
localized_strings,
|
||||
@ -578,6 +581,25 @@ impl<'a> Widget for Bag<'a> {
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.desc_text_color(TEXT_COLOR);
|
||||
|
||||
// Tooltips
|
||||
let item_tooltip2 = 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,
|
||||
)
|
||||
})
|
||||
.title_font_size(self.fonts.cyri.scale(15))
|
||||
.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,
|
||||
@ -816,11 +838,11 @@ impl<'a> Widget for Bag<'a> {
|
||||
.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(
|
||||
self.tooltip_manager,
|
||||
.with_item_tooltip(
|
||||
self.item_tooltip_manager,
|
||||
title,
|
||||
&*desc,
|
||||
&item_tooltip,
|
||||
&item_tooltip2,
|
||||
chest_q_col,
|
||||
)
|
||||
.set(state.ids.chest_slot, ui);
|
||||
|
375
voxygen/src/hud/item_info.rs
Normal file
375
voxygen/src/hud/item_info.rs
Normal file
@ -0,0 +1,375 @@
|
||||
use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::{animate_by_pulse, ItemImgs, ItemKey::Tool as ToolKey},
|
||||
util,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
hud::get_quality_col,
|
||||
i18n::Localization,
|
||||
ui::{fonts::Fonts, ImageFrame, Ingameable, Tooltip, TooltipManager, Tooltipable},
|
||||
};
|
||||
use client::Client;
|
||||
use common::{
|
||||
combat::{combat_rating, Damage},
|
||||
comp::item::{
|
||||
armor::{Armor, ArmorKind, Protection},
|
||||
tool::{Hands, StatKind, Stats, Tool, ToolKind},
|
||||
Item, ItemDesc, ItemKind, MaterialStatManifest, Quality,
|
||||
},
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
widget::{self, Button, Image, Rectangle, Scrollbar, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
|
||||
widget_ids! {
|
||||
pub struct Ids {
|
||||
title,
|
||||
subtitle,
|
||||
desc,
|
||||
stat1,
|
||||
stat2,
|
||||
stat3,
|
||||
diff1,
|
||||
diff2,
|
||||
diff3,
|
||||
item_frame,
|
||||
item_render,
|
||||
background,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct ItemInfo<'a> {
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
pulse: f32,
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
//rot_imgs: &'a ImgsRot,
|
||||
//tooltip_manager: &'a mut TooltipManager,
|
||||
localized_strings: &'a Localization,
|
||||
item: &'a Item,
|
||||
msm: &'a MaterialStatManifest,
|
||||
}
|
||||
|
||||
impl<'a> ItemInfo<'a> {
|
||||
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
pulse: f32,
|
||||
//rot_imgs: &'a ImgsRot,
|
||||
//tooltip_manager: &'a mut TooltipManager,
|
||||
localized_strings: &'a Localization,
|
||||
item: &'a Item,
|
||||
msm: &'a MaterialStatManifest,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
imgs,
|
||||
item_imgs,
|
||||
fonts,
|
||||
pulse,
|
||||
common: widget::CommonBuilder::default(),
|
||||
//rot_imgs,
|
||||
//tooltip_manager,
|
||||
localized_strings,
|
||||
item,
|
||||
msm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
ids: Ids,
|
||||
}
|
||||
|
||||
impl<'a> Ingameable for ItemInfo<'a> {
|
||||
fn prim_count(&self) -> usize {
|
||||
// Number of conrod primitives contained in the overitem display.
|
||||
// TODO maybe this could be done automatically?
|
||||
// - 2 Text for name
|
||||
// - 0 or 2 Rectangle and Text for button
|
||||
4 + match self.item.kind() {
|
||||
ItemKind::Tool(_) => 3,
|
||||
ItemKind::Armor(_) => 2,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
//Show(bool),
|
||||
}
|
||||
|
||||
impl<'a> Widget for ItemInfo<'a> {
|
||||
type Event = Option<Event>;
|
||||
type State = State;
|
||||
type Style = ();
|
||||
|
||||
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||
State {
|
||||
ids: Ids::new(id_gen),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_unit)] // TODO: Pending review in #587
|
||||
fn style(&self) -> Self::Style { () }
|
||||
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
let item = self.item;
|
||||
let _i18n = &self.localized_strings;
|
||||
|
||||
let inventories = self.client.inventories();
|
||||
let inventory = match inventories.get(self.client.entity()) {
|
||||
Some(l) => l,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let equip_slot = inventory.equipped_items_of_kind(self.item.kind().clone());
|
||||
|
||||
let (title, desc) = (item.name().to_string(), item.description().to_string());
|
||||
|
||||
let quality = get_quality_col(self.item);
|
||||
|
||||
let subtitle = util::kind_text(item.kind());
|
||||
|
||||
let text_color = conrod_core::color::WHITE;
|
||||
|
||||
let art_size = [64.0, 64.0];
|
||||
|
||||
/*// Apply transparency
|
||||
let color = style.color(ui.theme()).alpha(self.transparency);
|
||||
|
||||
// Background image frame
|
||||
self.image_frame
|
||||
.wh(rect.dim())
|
||||
.xy(rect.xy())
|
||||
.graphics_for(id)
|
||||
.parent(id)
|
||||
.color(color)
|
||||
.set(state.ids.image_frame, ui);*/
|
||||
|
||||
widget::Rectangle::fill([310.0, 310.0])
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 0.98))
|
||||
.depth(1.0)
|
||||
.set(state.ids.background, ui);
|
||||
|
||||
// Icon BG
|
||||
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,
|
||||
};
|
||||
Image::new(quality_col_img)
|
||||
.w_h(art_size[0] + 10.0, art_size[1] + 10.0)
|
||||
.top_left_with_margin_on(state.ids.background, 10.0)
|
||||
.set(state.ids.item_frame, ui);
|
||||
|
||||
// Icon
|
||||
Image::new(animate_by_pulse(
|
||||
&self.item_imgs.img_ids_or_not_found_img(item.into()),
|
||||
self.pulse,
|
||||
))
|
||||
.color(Some(conrod_core::color::WHITE))
|
||||
.wh(art_size)
|
||||
.middle_of(state.ids.item_frame)
|
||||
.set(state.ids.item_render, ui);
|
||||
|
||||
// Title
|
||||
Text::new(&title)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(25))
|
||||
.y_align_to(state.ids.item_frame, conrod_core::position::Align::End)
|
||||
.right_from(state.ids.item_frame, 10.0)
|
||||
.color(quality)
|
||||
.depth(2.0)
|
||||
.set(state.ids.title, ui);
|
||||
|
||||
// Subtitle
|
||||
Text::new(&subtitle)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(conrod_core::color::GREY)
|
||||
.depth(3.0)
|
||||
.set(state.ids.subtitle, ui);
|
||||
|
||||
// Stats
|
||||
match item.kind() {
|
||||
ItemKind::Tool(tool) => {
|
||||
let stat1 = tool.base_power(self.msm, item.components()) * 10.0;
|
||||
let stat2 = tool.base_speed(self.msm, item.components()) * 10.0;
|
||||
let stat3 = tool.base_poise_strength(self.msm, item.components()) * 10.0;
|
||||
|
||||
Text::new(&format!("Power : {}", stat1.to_string()))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
|
||||
.color(text_color)
|
||||
.depth(3.0)
|
||||
.set(state.ids.stat1, ui);
|
||||
Text::new(&format!("Speed : {}", stat2.to_string()))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(text_color)
|
||||
.depth(3.0)
|
||||
.set(state.ids.stat2, ui);
|
||||
Text::new(&format!("Poise : {}", stat3.to_string()))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(text_color)
|
||||
.depth(3.0)
|
||||
.set(state.ids.stat3, 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 diff1 = util::comparaison(tool_stats.power, equipped_tool_stats.power);
|
||||
let diff2 = util::comparaison(tool_stats.speed, equipped_tool_stats.speed);
|
||||
let diff3 = util::comparaison(
|
||||
tool_stats.poise_strength,
|
||||
equipped_tool_stats.poise_strength,
|
||||
);
|
||||
|
||||
Text::new(&format!("{} {:.1}", &diff1.0, &diff.power * 10.0))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(diff1.1)
|
||||
.align_middle_y_of(state.ids.stat1)
|
||||
.right_from(state.ids.stat1, 10.0)
|
||||
.depth(3.0)
|
||||
.set(state.ids.diff1, ui);
|
||||
Text::new(&format!("{} {:.1}", &diff2.0, &diff.speed * 10.0))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(diff2.1)
|
||||
.align_middle_y_of(state.ids.stat2)
|
||||
.right_from(state.ids.stat2, 10.0)
|
||||
.depth(3.0)
|
||||
.set(state.ids.diff2, ui);
|
||||
Text::new(&format!("{} {:.1}", &diff3.0, &diff.poise_strength * 10.0))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(diff3.1)
|
||||
.align_middle_y_of(state.ids.stat3)
|
||||
.right_from(state.ids.stat3, 10.0)
|
||||
.depth(3.0)
|
||||
.set(state.ids.diff3, ui);
|
||||
}
|
||||
}
|
||||
},
|
||||
ItemKind::Armor(armor) => {
|
||||
let stat1 = armor.get_protection();
|
||||
let stat2 = armor.get_poise_resilience();
|
||||
|
||||
Text::new(&format!("Armour : {}", util::protec2string(stat1)))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
|
||||
.color(text_color)
|
||||
.depth(3.0)
|
||||
.set(state.ids.stat1, ui);
|
||||
Text::new(&format!("Poise res : {}", util::protec2string(stat2)))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(text_color)
|
||||
.depth(3.0)
|
||||
.set(state.ids.stat2, 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 diff1 = util::comparaison(
|
||||
&armor.stats.protection,
|
||||
&equipped_armor.stats.protection,
|
||||
);
|
||||
let diff2 = util::comparaison(
|
||||
&armor.stats.poise_resilience,
|
||||
&equipped_armor.stats.poise_resilience,
|
||||
);
|
||||
|
||||
Text::new(&format!(
|
||||
"{} {}",
|
||||
&diff1.0,
|
||||
util::protec2string(diff.protection)
|
||||
))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(diff1.1)
|
||||
.align_middle_y_of(state.ids.stat1)
|
||||
.right_from(state.ids.stat1, 10.0)
|
||||
.depth(3.0)
|
||||
.set(state.ids.diff1, ui);
|
||||
Text::new(&format!(
|
||||
"{} {}",
|
||||
&diff2.0,
|
||||
util::protec2string(diff.protection)
|
||||
))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.color(diff2.1)
|
||||
.align_middle_y_of(state.ids.stat2)
|
||||
.right_from(state.ids.stat2, 10.0)
|
||||
.depth(3.0)
|
||||
.set(state.ids.diff2, ui);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Text::new(&desc)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(15))
|
||||
.x_align_to(state.ids.item_frame, conrod_core::position::Align::Start)
|
||||
.color(conrod_core::color::GREY)
|
||||
.depth(3.0)
|
||||
.w(300.0)
|
||||
.set(state.ids.desc, ui);
|
||||
|
||||
/*let test = widget::Text::new(&desc).w(300.0).get_h(ui);
|
||||
dbg!(test);*/
|
||||
|
||||
/*// Items
|
||||
let stats_count: usize = match item.kind() {
|
||||
ItemKind::Armor(armor) => 2,
|
||||
ItemKind::Tool(tool) => 4,
|
||||
_ => 0,
|
||||
};
|
||||
let gen = &mut ui.widget_id_generator();
|
||||
state.update(|state| state.ids.stats.resize(item_count, gen));
|
||||
state.update(|state| state.ids.stats_icons.resize(item_count, gen));
|
||||
|
||||
// Create Stats Widgets
|
||||
let stats_vec = state
|
||||
.ids
|
||||
.stats
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.stats_icons.iter().copied())
|
||||
.zip(stats)
|
||||
.collect::<Vec<_>>();*/
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ mod group;
|
||||
mod hotbar;
|
||||
mod img_ids;
|
||||
mod item_imgs;
|
||||
mod item_info;
|
||||
mod map;
|
||||
mod minimap;
|
||||
mod overhead;
|
||||
@ -37,6 +38,7 @@ use esc_menu::EscMenu;
|
||||
use group::Group;
|
||||
use img_ids::Imgs;
|
||||
use item_imgs::ItemImgs;
|
||||
use item_info::ItemInfo;
|
||||
use map::Map;
|
||||
use minimap::MiniMap;
|
||||
use popup::Popup;
|
||||
@ -263,6 +265,7 @@ widget_ids! {
|
||||
crafting_window,
|
||||
settings_window,
|
||||
group_window,
|
||||
item_info,
|
||||
|
||||
// Free look indicator
|
||||
free_look_txt,
|
||||
@ -903,7 +906,8 @@ 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();
|
||||
let (ref mut ui_widgets, ref mut item_tooltip_manager, ref mut tooltip_manager) = &mut self.ui.set_widgets();
|
||||
//let (ref mut ui_item_widgets, ref mut item_tooltip_manager) = &mut self.ui.set_item_widgets();
|
||||
// pulse time for pulsating elements
|
||||
self.pulse = self.pulse + dt.as_secs_f32();
|
||||
// FPS
|
||||
@ -1338,6 +1342,12 @@ impl Hud {
|
||||
&mut self.ids.overitems,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
|
||||
let overitem_id2 = overitem_walker.next(
|
||||
&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 +1356,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,
|
||||
@ -1356,6 +1369,19 @@ impl Hud {
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(ingame_pos)
|
||||
.set(overitem_id, ui_widgets);
|
||||
|
||||
item_info::ItemInfo::new(
|
||||
client,
|
||||
&self.imgs,
|
||||
&self.item_imgs,
|
||||
&self.fonts,
|
||||
self.pulse,
|
||||
i18n,
|
||||
item,
|
||||
&msm,
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
.set(overitem_id2, ui_widgets);
|
||||
}
|
||||
|
||||
let speech_bubbles = &self.speech_bubbles;
|
||||
@ -2239,6 +2265,7 @@ impl Hud {
|
||||
//&controller,
|
||||
&self.hotbar,
|
||||
tooltip_manager,
|
||||
item_tooltip_manager,
|
||||
&mut self.slot_manager,
|
||||
i18n,
|
||||
&ability_map,
|
||||
@ -2262,6 +2289,7 @@ impl Hud {
|
||||
&self.fonts,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
item_tooltip_manager,
|
||||
&mut self.slot_manager,
|
||||
self.pulse,
|
||||
i18n,
|
||||
|
@ -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)
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
ui::{
|
||||
fonts::Fonts,
|
||||
slot::{ContentSize, SlotMaker},
|
||||
ImageFrame, Tooltip, TooltipManager, Tooltipable,
|
||||
ImageFrame, Tooltip, TooltipManager, Tooltipable, ItemTooltip, ItemTooltipManager, ItemTooltipable,
|
||||
},
|
||||
window::GameInput,
|
||||
GlobalState,
|
||||
@ -142,6 +142,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,
|
||||
@ -168,6 +169,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,
|
||||
@ -189,6 +191,7 @@ impl<'a> Skillbar<'a> {
|
||||
// controller,
|
||||
hotbar,
|
||||
tooltip_manager,
|
||||
item_tooltip_manager,
|
||||
slot_manager,
|
||||
localized_strings,
|
||||
ability_map,
|
||||
@ -481,6 +484,25 @@ 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_tooltip2 = 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,
|
||||
)
|
||||
})
|
||||
.title_font_size(self.fonts.cyri.scale(15))
|
||||
.parent(ui.window)
|
||||
.desc_font_size(self.fonts.cyri.scale(12))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.desc_text_color(TEXT_COLOR);
|
||||
|
||||
// Helper
|
||||
let tooltip_text = |slot| {
|
||||
content_source
|
||||
@ -543,7 +565,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.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)
|
||||
slot.with_item_tooltip(self.item_tooltip_manager, title, desc, &item_tooltip2, TEXT_COLOR)
|
||||
.set(state.ids.slot1, ui);
|
||||
} else {
|
||||
slot.set(state.ids.slot1, ui);
|
||||
|
@ -28,7 +28,7 @@ pub fn loadout_slot_text<'a>(
|
||||
}
|
||||
|
||||
pub fn item_text<'a>(
|
||||
item: &'a impl ItemDesc,
|
||||
item: &'a dyn ItemDesc,
|
||||
msm: &'a MaterialStatManifest,
|
||||
) -> (&'a str, Cow<'a, str>) {
|
||||
let desc: Cow<str> = match item.kind() {
|
||||
@ -47,18 +47,18 @@ pub fn item_text<'a>(
|
||||
&msm,
|
||||
item.description(),
|
||||
)),
|
||||
ItemKind::Glider(_glider) => Cow::Owned(glider_desc(item.description())),
|
||||
ItemKind::Glider(_glider) => Cow::Owned(generic_desc(item)),
|
||||
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::Throwable { .. } => Cow::Owned(generic_desc(item)),
|
||||
ItemKind::Utility { .. } => Cow::Owned(generic_desc(item)),
|
||||
ItemKind::Ingredient { .. } => Cow::Owned(ingredient_desc(
|
||||
item.description(),
|
||||
item.item_definition_id(),
|
||||
msm,
|
||||
)),
|
||||
ItemKind::Lantern { .. } => Cow::Owned(lantern_desc(item.description())),
|
||||
ItemKind::Lantern { .. } => Cow::Owned(generic_desc(item)),
|
||||
ItemKind::TagExamples { .. } => Cow::Borrowed(item.description()),
|
||||
//_ => Cow::Borrowed(item.description()),
|
||||
};
|
||||
@ -80,6 +80,46 @@ pub fn append_price_desc(desc: &mut String, prices: &Option<SitePrices>, item_de
|
||||
}
|
||||
}
|
||||
|
||||
fn use_text(kind: &ItemKind) -> String {
|
||||
let text = match kind {
|
||||
ItemKind::Armor(_)
|
||||
| ItemKind::Tool(_)
|
||||
| ItemKind::ModularComponent(_)
|
||||
| ItemKind::Glider(_)
|
||||
| ItemKind::Consumable { .. }
|
||||
| ItemKind::Utility { .. }
|
||||
| ItemKind::Ingredient { .. }
|
||||
| ItemKind::Lantern { .. } => "<Right-Click to use>",
|
||||
ItemKind::Throwable { .. } => "<Right-Click to throw>",
|
||||
ItemKind::TagExamples { .. } => "",
|
||||
};
|
||||
text.to_string()
|
||||
}
|
||||
|
||||
pub fn kind_text(kind: &ItemKind) -> String {
|
||||
match kind {
|
||||
ItemKind::Armor(armor) => format!("Armor ({})", armor_kind(&armor)),
|
||||
ItemKind::Tool(tool) => format!("{} {}", tool_hands(&tool), tool_kind(&tool)),
|
||||
ItemKind::ModularComponent(_mc) => "Modular Component".to_string(),
|
||||
ItemKind::Glider(_glider) => "Glider".to_string(),
|
||||
ItemKind::Consumable { .. } => "Consumable".to_string(),
|
||||
ItemKind::Throwable { .. } => "Can be thrown".to_string(),
|
||||
ItemKind::Utility { .. } => "Utility".to_string(),
|
||||
ItemKind::Ingredient { .. } => "Ingredient".to_string(),
|
||||
ItemKind::Lantern { .. } => "Lantern".to_string(),
|
||||
ItemKind::TagExamples { .. } => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generic_desc(desc: &dyn ItemDesc) -> String {
|
||||
format!(
|
||||
"{}\n\n{}\n\n{}",
|
||||
kind_text(desc.kind()),
|
||||
desc.description(),
|
||||
use_text(desc.kind())
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: localization
|
||||
fn modular_component_desc(
|
||||
mc: &ModularComponent,
|
||||
@ -100,7 +140,6 @@ 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
|
||||
@ -165,12 +204,6 @@ fn consumable_desc(effects: &[Effect], desc: &str) -> String {
|
||||
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) {
|
||||
@ -180,10 +213,9 @@ fn ingredient_desc(desc: &str, item_id: &str, msm: &MaterialStatManifest) -> Str
|
||||
result
|
||||
}
|
||||
|
||||
fn lantern_desc(desc: &str) -> String { format!("Lantern\n\n{}\n\n<Right-Click to use>", desc) }
|
||||
// Armor
|
||||
|
||||
fn armor_desc(armor: &Armor, desc: &str, slots: u16) -> String {
|
||||
// TODO: localization
|
||||
fn armor_kind(armor: &Armor) -> String {
|
||||
let kind = match armor.kind {
|
||||
ArmorKind::Shoulder(_) => "Shoulders",
|
||||
ArmorKind::Chest(_) => "Chest",
|
||||
@ -198,33 +230,45 @@ fn armor_desc(armor: &Armor, desc: &str, slots: u16) -> String {
|
||||
ArmorKind::Tabard(_) => "Tabard",
|
||||
ArmorKind::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.to_string()
|
||||
}
|
||||
|
||||
fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc: &str) -> String {
|
||||
fn armor_protection(armor: &Armor) -> String {
|
||||
match armor.get_protection() {
|
||||
Protection::Normal(a) => format!("Protection: {}", a.to_string()),
|
||||
Protection::Invincible => "Protection: Inf".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn armor_desc(armor: &Armor, desc: &str, slots: u16) -> String {
|
||||
// TODO: localization
|
||||
let kind = armor_kind(armor);
|
||||
let armor_protection = armor_protection(armor);
|
||||
//let armor_poise_resilience = match armor.get_poise_resilience() {
|
||||
// Protection::Normal(a) => a.to_string(),
|
||||
// Protection::Invincible => "Inf".to_string(),
|
||||
//};
|
||||
|
||||
let mut desctext: String = "".to_string();
|
||||
if !desc.is_empty() {
|
||||
desctext = desc.to_string();
|
||||
}
|
||||
|
||||
let mut slottext: String = "".to_string();
|
||||
if slots > 0 {
|
||||
slottext = format!("Slots: {}", slots)
|
||||
}
|
||||
|
||||
let usetext = use_text(&ItemKind::Armor(armor.clone()));
|
||||
format!(
|
||||
"{} {}\n\n{}\n{}\n{}",
|
||||
kind, armor_protection, slottext, desctext, usetext
|
||||
)
|
||||
}
|
||||
|
||||
//Tool
|
||||
|
||||
pub fn tool_kind(tool: &Tool) -> String {
|
||||
let kind = match tool.kind {
|
||||
ToolKind::Sword => "Sword",
|
||||
ToolKind::Axe => "Axe",
|
||||
@ -246,35 +290,63 @@ fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc:
|
||||
ToolKind::Pick => "Pickaxe",
|
||||
ToolKind::Empty => "Empty",
|
||||
};
|
||||
kind.to_string()
|
||||
}
|
||||
|
||||
// Get tool stats
|
||||
pub fn tool_stats(tool: &Tool, components: &[Item], msm: &MaterialStatManifest) -> String {
|
||||
let stats = tool.stats.resolve_stats(msm, components).clamp_speed();
|
||||
statblock_desc(&stats)
|
||||
}
|
||||
|
||||
pub fn tool_hands(tool: &Tool) -> String {
|
||||
let hands = match tool.hands {
|
||||
Hands::One => "One",
|
||||
Hands::Two => "Two",
|
||||
Hands::One => "One-Handed",
|
||||
Hands::Two => "Two-Handed",
|
||||
};
|
||||
hands.to_string()
|
||||
}
|
||||
|
||||
let mut result = format!("{}-Handed {}\n\n", hands, kind);
|
||||
result += &statblock_desc(&stats);
|
||||
fn components_list(components: &[Item]) -> String {
|
||||
let mut text: String = "Made from:\n".to_string();
|
||||
for component in components {
|
||||
text += component.name();
|
||||
text += "\n"
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
pub fn tool_desc(
|
||||
tool: &Tool,
|
||||
components: &[Item],
|
||||
msm: &MaterialStatManifest,
|
||||
desc: &str,
|
||||
) -> String {
|
||||
let kind = tool_kind(tool);
|
||||
//let poise_strength = tool.base_poise_strength();
|
||||
let hands = tool_hands(tool);
|
||||
let stats = tool_stats(tool, components, msm);
|
||||
let usetext = use_text(&ItemKind::Tool(tool.clone()));
|
||||
let mut componentstext: String = "".to_string();
|
||||
if !components.is_empty() {
|
||||
result += "Made from:\n";
|
||||
for component in components {
|
||||
result += component.name();
|
||||
result += "\n"
|
||||
}
|
||||
result += "\n";
|
||||
componentstext = components_list(components);
|
||||
}
|
||||
let mut desctext: String = "".to_string();
|
||||
if !desc.is_empty() {
|
||||
result += &format!("{}\n\n", desc);
|
||||
desctext = desc.to_string();
|
||||
}
|
||||
result += "<Right-Click to use>";
|
||||
result
|
||||
format!(
|
||||
"{} {}\n\n{}\n{}\n{}\n{}",
|
||||
hands, kind, stats, componentstext, desctext, usetext
|
||||
)
|
||||
}
|
||||
|
||||
fn statblock_desc(stats: &Stats) -> String {
|
||||
format!(
|
||||
"Power: {:0.1}\n\nPoise Strength: {:0.1}\n\nSpeed: {:0.1}\n\n",
|
||||
"DPS: {:0.1}\nPower: {:0.1}\nSpeed: {:0.1}\n",
|
||||
// add back when ready for poise
|
||||
//"{}\n\nDPS: {:0.1}\n\nPower: {:0.1}\n\nPoise Strength: {:0.1}\n\nSpeed: \
|
||||
// {:0.1}\n\n{}\n\n<Right-Click to use>",
|
||||
stats.speed * stats.power * 10.0, // Damage per second
|
||||
stats.power * 10.0,
|
||||
stats.poise_strength * 10.0,
|
||||
stats.speed,
|
||||
@ -285,6 +357,24 @@ fn statblock_desc(stats: &Stats) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
// Compare two type, output a colored character to show comparison
|
||||
pub fn comparaison<T: PartialOrd>(first: T, other: T) -> (String, conrod_core::color::Color) {
|
||||
if first == other {
|
||||
(".".to_string(), conrod_core::color::GREY)
|
||||
} else if other < first {
|
||||
("^".to_string(), conrod_core::color::GREEN)
|
||||
} else {
|
||||
("v".to_string(), conrod_core::color::RED)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn protec2string(stat: Protection) -> String {
|
||||
match stat {
|
||||
Protection::Normal(a) => format!("{:.1}", a),
|
||||
Protection::Invincible => "Infinite".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -20,6 +20,7 @@ pub use widgets::{
|
||||
slot,
|
||||
toggle_button::ToggleButton,
|
||||
tooltip::{Tooltip, TooltipManager, Tooltipable},
|
||||
item_tooltip::{ItemTooltip, ItemTooltipManager, ItemTooltipable},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -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,12 @@ 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 +308,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)
|
||||
|
508
voxygen/src/ui/widgets/item_tooltip.rs
Normal file
508
voxygen/src/ui/widgets/item_tooltip.rs
Normal file
@ -0,0 +1,508 @@
|
||||
use super::image_frame::ImageFrame;
|
||||
use conrod_core::{
|
||||
builder_method, builder_methods, image, input::global::Global, position::Dimension, text,
|
||||
widget, widget_ids, Color, Colorable, FontSize, Positionable, Sizeable, Ui, UiCell, Widget,
|
||||
WidgetCommon, WidgetStyle,
|
||||
};
|
||||
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;
|
||||
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); // Default text color
|
||||
|
||||
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,
|
||||
title_text: &str,
|
||||
desc_text: &str,
|
||||
title_col: Color,
|
||||
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()
|
||||
.title(title_text)
|
||||
.desc(desc_text)
|
||||
.title_col(title_col)
|
||||
.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,
|
||||
title_text: &'a str,
|
||||
desc_text: &'a str,
|
||||
img_id: Option<image::Id>,
|
||||
image_dims: Option<(f64, f64)>,
|
||||
tooltip: &'a ItemTooltip<'a>,
|
||||
title_col: Color,
|
||||
}
|
||||
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.title_text,
|
||||
self.desc_text,
|
||||
self.title_col,
|
||||
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,
|
||||
title_text: &'a str,
|
||||
desc_text: &'a str,
|
||||
tooltip: &'a ItemTooltip<'a>,
|
||||
title_col: Color,
|
||||
) -> 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,
|
||||
title_text: &'a str,
|
||||
desc_text: &'a str,
|
||||
tooltip: &'a ItemTooltip<'a>,
|
||||
title_col: Color,
|
||||
) -> ItemTooltipped<'a, W> {
|
||||
ItemTooltipped {
|
||||
inner: self,
|
||||
tooltip_manager,
|
||||
title_text,
|
||||
desc_text,
|
||||
img_id: None,
|
||||
image_dims: None,
|
||||
tooltip,
|
||||
title_col,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
/// Default portion of inner width that goes to an image
|
||||
const IMAGE_W_FRAC: f64 = 0.3;
|
||||
/// Default width multiplied by the description font size
|
||||
const DEFAULT_CHAR_W: f64 = 30.0;
|
||||
/// Text vertical spacing factor to account for overhanging text
|
||||
const TEXT_SPACE_FACTOR: f64 = 0.35;
|
||||
|
||||
/// A widget for displaying tooltips
|
||||
#[derive(Clone, WidgetCommon)]
|
||||
pub struct ItemTooltip<'a> {
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
title_text: &'a str,
|
||||
desc_text: &'a str,
|
||||
title_col: Color,
|
||||
image: Option<image::Id>,
|
||||
image_dims: Option<(f64, f64)>,
|
||||
style: Style,
|
||||
transparency: f32,
|
||||
image_frame: ImageFrame,
|
||||
}
|
||||
|
||||
#[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,
|
||||
desc,
|
||||
image_frame,
|
||||
image,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
ids: Ids,
|
||||
}
|
||||
|
||||
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) }
|
||||
image { image = Option<image::Id> }
|
||||
title { title_text = &'a str }
|
||||
desc { desc_text = &'a str }
|
||||
image_dims { image_dims = Option<(f64, f64)> }
|
||||
transparency { transparency = f32 }
|
||||
title_col { title_col = Color}
|
||||
}
|
||||
|
||||
pub fn new(image_frame: ImageFrame) -> Self {
|
||||
ItemTooltip {
|
||||
common: widget::CommonBuilder::default(),
|
||||
style: Style::default(),
|
||||
title_text: "",
|
||||
desc_text: "",
|
||||
transparency: 1.0,
|
||||
image_frame,
|
||||
image: None,
|
||||
image_dims: None,
|
||||
title_col: TEXT_COLOR,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
style,
|
||||
ui,
|
||||
..
|
||||
} = args;
|
||||
|
||||
// Widths
|
||||
let (text_w, image_w) = self.text_image_width(rect.w());
|
||||
|
||||
// Apply transparency
|
||||
let color = style.color(ui.theme()).alpha(self.transparency);
|
||||
|
||||
// Background image frame
|
||||
self.image_frame
|
||||
.wh(rect.dim())
|
||||
.xy(rect.xy())
|
||||
.graphics_for(id)
|
||||
.parent(id)
|
||||
.color(color)
|
||||
.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(color))
|
||||
.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
|
||||
.set(state.ids.image, ui);
|
||||
}
|
||||
|
||||
// Spacing for overhanging text
|
||||
let title_space = self.style.title.font_size(&ui.theme) as f64 * TEXT_SPACE_FACTOR;
|
||||
|
||||
// Title of tooltip
|
||||
if !self.title_text.is_empty() {
|
||||
let title = widget::Text::new(self.title_text)
|
||||
.w(text_w)
|
||||
.graphics_for(id)
|
||||
.parent(id)
|
||||
.with_style(self.style.title)
|
||||
// Apply transparency
|
||||
.color(self.title_col);
|
||||
|
||||
if self.image.is_some() {
|
||||
title
|
||||
.right_from(state.ids.image, H_PAD)
|
||||
.align_top_of(state.ids.image)
|
||||
} else {
|
||||
title.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
|
||||
}
|
||||
.set(state.ids.title, ui);
|
||||
}
|
||||
|
||||
// Description of tooltip
|
||||
let desc = widget::Text::new(self.desc_text)
|
||||
.w(text_w)
|
||||
.graphics_for(id)
|
||||
.parent(id)
|
||||
// Apply transparency
|
||||
.color(style.desc.color(ui.theme()).alpha(self.transparency))
|
||||
.with_style(self.style.desc);
|
||||
|
||||
if !self.title_text.is_empty() {
|
||||
desc.down_from(state.ids.title, V_PAD * 0.5 + title_space)
|
||||
.align_left_of(state.ids.title)
|
||||
} else if self.image.is_some() {
|
||||
desc.right_from(state.ids.image, H_PAD)
|
||||
.align_top_of(state.ids.image)
|
||||
} else {
|
||||
desc.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
|
||||
}
|
||||
.set(state.ids.desc, 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 {
|
||||
let single_line_title_w = widget::Text::new(self.title_text)
|
||||
.with_style(self.style.title)
|
||||
.get_w(ui)
|
||||
.unwrap_or(0.0);
|
||||
let single_line_desc_w = widget::Text::new(self.desc_text)
|
||||
.with_style(self.style.desc)
|
||||
.get_w(ui)
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let text_w = single_line_title_w.max(single_line_desc_w);
|
||||
let inner_w = if self.image.is_some() {
|
||||
match self.image_dims {
|
||||
Some((w, _)) => w + text_w + H_PAD,
|
||||
None => text_w / (1.0 - IMAGE_W_FRAC) + H_PAD,
|
||||
}
|
||||
} else {
|
||||
text_w
|
||||
};
|
||||
|
||||
let width =
|
||||
inner_w.min(self.style.desc.font_size(&ui.theme) as f64 * DEFAULT_CHAR_W) + 2.0 * H_PAD;
|
||||
Dimension::Absolute(width)
|
||||
}
|
||||
|
||||
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
|
||||
let (text_w, image_w) = self.text_image_width(self.get_w(ui).unwrap_or(0.0));
|
||||
let title_h = if self.title_text.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
widget::Text::new(self.title_text)
|
||||
.with_style(self.style.title)
|
||||
.w(text_w)
|
||||
.get_h(ui)
|
||||
.unwrap_or(0.0)
|
||||
+ self.style.title.font_size(&ui.theme) as f64 * TEXT_SPACE_FACTOR
|
||||
+ 0.5 * V_PAD
|
||||
};
|
||||
let desc_h = if self.desc_text.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
widget::Text::new(self.desc_text)
|
||||
.with_style(self.style.desc)
|
||||
.w(text_w)
|
||||
.get_h(ui)
|
||||
.unwrap_or(0.0)
|
||||
+ self.style.desc.font_size(&ui.theme) as f64 * TEXT_SPACE_FACTOR
|
||||
};
|
||||
// Image defaults to square shape
|
||||
let image_h = self.image_dims.map_or(image_w, |(_, h)| h);
|
||||
// Title height + desc height + padding/spacing
|
||||
let height = (title_h + desc_h).max(image_h) + 2.0 * V_PAD;
|
||||
Dimension::Absolute(height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Colorable for ItemTooltip<'a> {
|
||||
builder_method!(color { style.color = Some(Color) });
|
||||
}
|
@ -6,3 +6,4 @@ pub mod radio_list;
|
||||
pub mod slot;
|
||||
pub mod toggle_button;
|
||||
pub mod tooltip;
|
||||
pub mod item_tooltip;
|
Loading…
Reference in New Issue
Block a user