New tooltip

This commit is contained in:
Snowram 2021-03-02 01:45:02 +01:00 committed by Robin Gilh
parent 88cdd24847
commit 4786f84afd
13 changed files with 1209 additions and 72 deletions

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 {
@ -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 {

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

@ -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);

View 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
}
}

View File

@ -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,

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,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);

View File

@ -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::*;

View File

@ -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)

View 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) });
}

View File

@ -6,3 +6,4 @@ pub mod radio_list;
pub mod slot;
pub mod toggle_button;
pub mod tooltip;
pub mod item_tooltip;