mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
1017 lines
40 KiB
Rust
1017 lines
40 KiB
Rust
use super::{
|
|
get_quality_col,
|
|
img_ids::{Imgs, ImgsRot},
|
|
item_imgs::{animate_by_pulse, ItemImgs, ItemKey::Tool},
|
|
Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
|
};
|
|
use crate::ui::{
|
|
fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip,
|
|
TooltipManager, Tooltipable,
|
|
};
|
|
use client::{self, Client};
|
|
use common::{
|
|
assets::AssetExt,
|
|
comp::{
|
|
item::{
|
|
ItemDef, ItemDesc, ItemKind, ItemTag, MaterialStatManifest, Quality, TagExampleInfo,
|
|
},
|
|
Inventory,
|
|
},
|
|
recipe::{Recipe, RecipeInput},
|
|
terrain::SpriteKind,
|
|
};
|
|
use conrod_core::{
|
|
color, image,
|
|
position::Dimension,
|
|
widget::{self, Button, Image, Rectangle, Scrollbar, Text, TextEdit},
|
|
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
|
|
};
|
|
use i18n::Localization;
|
|
use std::sync::Arc;
|
|
|
|
use strum::IntoEnumIterator;
|
|
use strum_macros::EnumIter;
|
|
|
|
widget_ids! {
|
|
pub struct Ids {
|
|
window,
|
|
window_frame,
|
|
close,
|
|
icon,
|
|
title_main,
|
|
title_rec,
|
|
align_rec,
|
|
scrollbar_rec,
|
|
btn_open_search,
|
|
btn_close_search,
|
|
input_search,
|
|
input_bg_search,
|
|
input_overlay_search,
|
|
title_ing,
|
|
tags_ing[],
|
|
align_ing,
|
|
scrollbar_ing,
|
|
btn_craft,
|
|
recipe_list_btns[],
|
|
recipe_list_labels[],
|
|
recipe_list_quality_indicators[],
|
|
recipe_list_materials_indicators[],
|
|
recipe_img_frame[],
|
|
recipe_img[],
|
|
ingredients[],
|
|
ingredient_frame[],
|
|
ingredient_img[],
|
|
req_text[],
|
|
ingredients_txt,
|
|
req_station_title,
|
|
req_station_img,
|
|
req_station_txt,
|
|
output_img_frame,
|
|
output_img,
|
|
output_amount,
|
|
category_bgs[],
|
|
category_tabs[],
|
|
category_imgs[],
|
|
}
|
|
}
|
|
|
|
pub enum Event {
|
|
CraftRecipe(String),
|
|
ChangeCraftingTab(CraftingTab),
|
|
Close,
|
|
Focus(widget::Id),
|
|
SearchRecipe(Option<String>),
|
|
}
|
|
|
|
#[derive(WidgetCommon)]
|
|
pub struct Crafting<'a> {
|
|
client: &'a Client,
|
|
imgs: &'a Imgs,
|
|
fonts: &'a Fonts,
|
|
localized_strings: &'a Localization,
|
|
pulse: f32,
|
|
rot_imgs: &'a ImgsRot,
|
|
item_tooltip_manager: &'a mut ItemTooltipManager,
|
|
item_imgs: &'a ItemImgs,
|
|
inventory: &'a Inventory,
|
|
msm: &'a MaterialStatManifest,
|
|
#[conrod(common_builder)]
|
|
common: widget::CommonBuilder,
|
|
tooltip_manager: &'a mut TooltipManager,
|
|
show: &'a mut Show,
|
|
}
|
|
#[allow(clippy::too_many_arguments)]
|
|
impl<'a> Crafting<'a> {
|
|
pub fn new(
|
|
client: &'a Client,
|
|
imgs: &'a Imgs,
|
|
fonts: &'a Fonts,
|
|
localized_strings: &'a Localization,
|
|
pulse: f32,
|
|
rot_imgs: &'a ImgsRot,
|
|
item_tooltip_manager: &'a mut ItemTooltipManager,
|
|
item_imgs: &'a ItemImgs,
|
|
inventory: &'a Inventory,
|
|
msm: &'a MaterialStatManifest,
|
|
tooltip_manager: &'a mut TooltipManager,
|
|
show: &'a mut Show,
|
|
) -> Self {
|
|
Self {
|
|
client,
|
|
imgs,
|
|
fonts,
|
|
localized_strings,
|
|
pulse,
|
|
rot_imgs,
|
|
item_tooltip_manager,
|
|
tooltip_manager,
|
|
item_imgs,
|
|
inventory,
|
|
msm,
|
|
show,
|
|
common: widget::CommonBuilder::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, EnumIter, PartialEq)]
|
|
pub enum CraftingTab {
|
|
All,
|
|
Tool,
|
|
Armor,
|
|
Weapon,
|
|
ProcessedMaterial,
|
|
Dismantle,
|
|
Food,
|
|
Potion,
|
|
Bag,
|
|
Utility,
|
|
Glider,
|
|
}
|
|
|
|
impl CraftingTab {
|
|
fn name_key(self) -> &'static str {
|
|
match self {
|
|
CraftingTab::All => "hud.crafting.tabs.all",
|
|
CraftingTab::Armor => "hud.crafting.tabs.armor",
|
|
CraftingTab::Dismantle => "hud.crafting.tabs.dismantle",
|
|
CraftingTab::Food => "hud.crafting.tabs.food",
|
|
CraftingTab::Glider => "hud.crafting.tabs.glider",
|
|
CraftingTab::Potion => "hud.crafting.tabs.potion",
|
|
CraftingTab::Tool => "hud.crafting.tabs.tool",
|
|
CraftingTab::Utility => "hud.crafting.tabs.utility",
|
|
CraftingTab::Weapon => "hud.crafting.tabs.weapon",
|
|
CraftingTab::Bag => "hud.crafting.tabs.bag",
|
|
CraftingTab::ProcessedMaterial => "hud.crafting.tabs.processed_material",
|
|
}
|
|
}
|
|
|
|
fn img_id(self, imgs: &Imgs) -> image::Id {
|
|
match self {
|
|
CraftingTab::All => imgs.icon_globe,
|
|
CraftingTab::Armor => imgs.icon_armor,
|
|
CraftingTab::Dismantle => imgs.icon_dismantle,
|
|
CraftingTab::Food => imgs.icon_food,
|
|
CraftingTab::Glider => imgs.icon_glider,
|
|
CraftingTab::Potion => imgs.icon_potion,
|
|
CraftingTab::Tool => imgs.icon_tools,
|
|
CraftingTab::Utility => imgs.icon_utility,
|
|
CraftingTab::Weapon => imgs.icon_weapon,
|
|
CraftingTab::Bag => imgs.icon_bag,
|
|
CraftingTab::ProcessedMaterial => imgs.icon_processed_material,
|
|
}
|
|
}
|
|
|
|
fn satisfies(self, recipe: &Recipe) -> bool {
|
|
let (item, _count) = &recipe.output;
|
|
match self {
|
|
CraftingTab::All => true,
|
|
CraftingTab::Food => item.tags().contains(&ItemTag::Food),
|
|
CraftingTab::Armor => match item.kind() {
|
|
ItemKind::Armor(_) => !item.tags().contains(&ItemTag::Bag),
|
|
_ => false,
|
|
},
|
|
CraftingTab::Glider => matches!(item.kind(), ItemKind::Glider(_)),
|
|
CraftingTab::Potion => item.tags().contains(&ItemTag::Potion),
|
|
CraftingTab::ProcessedMaterial => {
|
|
(item.tags().contains(&ItemTag::MetalIngot)
|
|
|| item.tags().contains(&ItemTag::Textile)
|
|
|| item.tags().contains(&ItemTag::Leather)
|
|
|| item.tags().contains(&ItemTag::BaseMaterial))
|
|
&& !recipe.is_recycling
|
|
},
|
|
CraftingTab::Bag => item.tags().contains(&ItemTag::Bag),
|
|
CraftingTab::Tool => item.tags().contains(&ItemTag::CraftingTool),
|
|
CraftingTab::Utility => item.tags().contains(&ItemTag::Utility),
|
|
CraftingTab::Weapon => match item.kind() {
|
|
ItemKind::Tool(_) => !item.tags().contains(&ItemTag::CraftingTool),
|
|
_ => false,
|
|
},
|
|
CraftingTab::Dismantle => recipe.is_recycling,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
ids: Ids,
|
|
selected_recipe: Option<String>,
|
|
}
|
|
|
|
impl<'a> Widget for Crafting<'a> {
|
|
type Event = Vec<Event>;
|
|
type State = State;
|
|
type Style = ();
|
|
|
|
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
|
State {
|
|
ids: Ids::new(id_gen),
|
|
selected_recipe: None,
|
|
}
|
|
}
|
|
|
|
fn style(&self) -> Self::Style {}
|
|
|
|
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
|
common_base::prof_span!("Crafting::update");
|
|
let widget::UpdateArgs { state, ui, .. } = args;
|
|
|
|
let mut events = Vec::new();
|
|
|
|
// Tooltips
|
|
let item_tooltip = ItemTooltip::new(
|
|
{
|
|
// Edge images [t, b, r, l]
|
|
// Corner images [tr, tl, br, bl]
|
|
let edge = &self.rot_imgs.tt_side;
|
|
let corner = &self.rot_imgs.tt_corner;
|
|
ImageFrame::new(
|
|
[edge.cw180, edge.none, edge.cw270, edge.cw90],
|
|
[corner.none, corner.cw270, corner.cw90, corner.cw180],
|
|
Color::Rgba(0.08, 0.07, 0.04, 1.0),
|
|
5.0,
|
|
)
|
|
},
|
|
self.client,
|
|
self.imgs,
|
|
self.item_imgs,
|
|
self.pulse,
|
|
self.msm,
|
|
self.localized_strings,
|
|
)
|
|
.title_font_size(self.fonts.cyri.scale(20))
|
|
.parent(ui.window)
|
|
.desc_font_size(self.fonts.cyri.scale(12))
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.desc_text_color(TEXT_COLOR);
|
|
// Tab tooltips
|
|
let tabs_tooltip = Tooltip::new({
|
|
// Edge images [t, b, r, l]
|
|
// Corner images [tr, tl, br, bl]
|
|
let edge = &self.rot_imgs.tt_side;
|
|
let corner = &self.rot_imgs.tt_corner;
|
|
ImageFrame::new(
|
|
[edge.cw180, edge.none, edge.cw270, edge.cw90],
|
|
[corner.none, corner.cw270, corner.cw90, corner.cw180],
|
|
Color::Rgba(0.08, 0.07, 0.04, 1.0),
|
|
5.0,
|
|
)
|
|
})
|
|
.title_font_size(self.fonts.cyri.scale(15))
|
|
.parent(ui.window)
|
|
.desc_font_size(self.fonts.cyri.scale(12))
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.desc_text_color(TEXT_COLOR);
|
|
|
|
// Frame and window
|
|
Image::new(self.imgs.crafting_window)
|
|
.bottom_right_with_margins_on(ui.window, 308.0, 450.0)
|
|
.color(Some(UI_MAIN))
|
|
.w_h(470.0, 460.0)
|
|
.set(state.ids.window, ui);
|
|
// Window
|
|
Image::new(self.imgs.crafting_frame)
|
|
.middle_of(state.ids.window)
|
|
.color(Some(UI_HIGHLIGHT_0))
|
|
.wh_of(state.ids.window)
|
|
.set(state.ids.window_frame, ui);
|
|
|
|
// Crafting Icon
|
|
Image::new(self.imgs.crafting_icon_bordered)
|
|
.w_h(38.0, 38.0)
|
|
.top_left_with_margins_on(state.ids.window_frame, 4.0, 4.0)
|
|
.set(state.ids.icon, ui);
|
|
|
|
// Close Button
|
|
if Button::image(self.imgs.close_button)
|
|
.w_h(24.0, 25.0)
|
|
.hover_image(self.imgs.close_button_hover)
|
|
.press_image(self.imgs.close_button_press)
|
|
.top_right_with_margins_on(state.ids.window, 0.0, 0.0)
|
|
.set(state.ids.close, ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::Close);
|
|
}
|
|
|
|
// Title
|
|
Text::new(self.localized_strings.get("hud.crafting"))
|
|
.mid_top_with_margin_on(state.ids.window_frame, 9.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(20))
|
|
.color(TEXT_COLOR)
|
|
.set(state.ids.title_main, ui);
|
|
|
|
// Alignment
|
|
Rectangle::fill_with([184.0, 378.0], color::TRANSPARENT)
|
|
.top_left_with_margins_on(state.ids.window_frame, 74.0, 5.0)
|
|
.scroll_kids_vertically()
|
|
.set(state.ids.align_rec, ui);
|
|
Rectangle::fill_with([274.0, 340.0], color::TRANSPARENT)
|
|
.top_right_with_margins_on(state.ids.window, 74.0, 5.0)
|
|
.scroll_kids_vertically()
|
|
.set(state.ids.align_ing, ui);
|
|
|
|
// Category Tabs
|
|
if state.ids.category_bgs.len() < CraftingTab::iter().enumerate().len() {
|
|
state.update(|s| {
|
|
s.ids.category_bgs.resize(
|
|
CraftingTab::iter().enumerate().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
})
|
|
};
|
|
if state.ids.category_tabs.len() < CraftingTab::iter().enumerate().len() {
|
|
state.update(|s| {
|
|
s.ids.category_tabs.resize(
|
|
CraftingTab::iter().enumerate().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
})
|
|
};
|
|
if state.ids.category_imgs.len() < CraftingTab::iter().enumerate().len() {
|
|
state.update(|s| {
|
|
s.ids.category_imgs.resize(
|
|
CraftingTab::iter().enumerate().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
})
|
|
};
|
|
let sel_crafting_tab = &self.show.crafting_tab;
|
|
for (i, crafting_tab) in CraftingTab::iter().enumerate() {
|
|
let tab_img = crafting_tab.img_id(self.imgs);
|
|
// Button Background
|
|
let mut bg = Image::new(self.imgs.pixel)
|
|
.w_h(40.0, 30.0)
|
|
.color(Some(UI_MAIN));
|
|
if i == 0 {
|
|
bg = bg.top_left_with_margins_on(state.ids.window_frame, 50.0, -40.0)
|
|
} else {
|
|
bg = bg.down_from(state.ids.category_bgs[i - 1], 0.0)
|
|
};
|
|
bg.set(state.ids.category_bgs[i], ui);
|
|
// Category Button
|
|
if Button::image(if crafting_tab == *sel_crafting_tab {
|
|
self.imgs.wpn_icon_border_pressed
|
|
} else {
|
|
self.imgs.wpn_icon_border
|
|
})
|
|
.wh_of(state.ids.category_bgs[i])
|
|
.middle_of(state.ids.category_bgs[i])
|
|
.hover_image(if crafting_tab == *sel_crafting_tab {
|
|
self.imgs.wpn_icon_border_pressed
|
|
} else {
|
|
self.imgs.wpn_icon_border_mo
|
|
})
|
|
.press_image(if crafting_tab == *sel_crafting_tab {
|
|
self.imgs.wpn_icon_border_pressed
|
|
} else {
|
|
self.imgs.wpn_icon_border_press
|
|
})
|
|
.with_tooltip(
|
|
self.tooltip_manager,
|
|
self.localized_strings.get(crafting_tab.name_key()),
|
|
"",
|
|
&tabs_tooltip,
|
|
TEXT_COLOR,
|
|
)
|
|
.set(state.ids.category_tabs[i], ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::ChangeCraftingTab(crafting_tab))
|
|
};
|
|
// Tab images
|
|
Image::new(tab_img)
|
|
.middle_of(state.ids.category_tabs[i])
|
|
.w_h(20.0, 20.0)
|
|
.graphics_for(state.ids.category_tabs[i])
|
|
.set(state.ids.category_imgs[i], ui);
|
|
}
|
|
|
|
// First available recipes, then ones with available materials,
|
|
// then unavailable ones, each sorted by quality and then alphabetically
|
|
// In the tuple, "name" is the recipe book key, and "recipe.output.0.name()"
|
|
// is the display name (as stored in the item descriptors)
|
|
let mut ordered_recipes: Vec<_> = self
|
|
.client
|
|
.recipe_book()
|
|
.iter()
|
|
.filter(|(_, recipe)| {
|
|
let output_name = recipe.output.0.name.to_lowercase();
|
|
if let Some(key) = &self.show.crafting_search_key {
|
|
key.as_str()
|
|
.to_lowercase()
|
|
.split_whitespace()
|
|
.all(|substring| output_name.contains(substring))
|
|
} else {
|
|
true
|
|
}
|
|
})
|
|
.map(|(name, recipe)| {
|
|
let has_materials = self.client.available_recipes().get(name.as_str()).is_some();
|
|
let is_craftable =
|
|
self.client
|
|
.available_recipes()
|
|
.get(name.as_str())
|
|
.map_or(false, |cs| {
|
|
cs.map_or(true, |cs| {
|
|
Some(cs) == self.show.craft_sprite.map(|(_, s)| s)
|
|
})
|
|
});
|
|
(name, recipe, is_craftable, has_materials)
|
|
})
|
|
.collect();
|
|
ordered_recipes.sort_by_key(|(_, recipe, is_craftable, has_materials)| {
|
|
(
|
|
!is_craftable,
|
|
!has_materials,
|
|
recipe.output.0.quality(),
|
|
recipe.output.0.name(),
|
|
)
|
|
});
|
|
|
|
// Recipe list
|
|
if state.ids.recipe_list_btns.len() < self.client.recipe_book().iter().len() {
|
|
state.update(|state| {
|
|
state.ids.recipe_list_btns.resize(
|
|
self.client.recipe_book().iter().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
});
|
|
}
|
|
if state.ids.recipe_list_labels.len() < self.client.recipe_book().iter().len() {
|
|
state.update(|state| {
|
|
state.ids.recipe_list_labels.resize(
|
|
self.client.recipe_book().iter().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
});
|
|
}
|
|
if state.ids.recipe_list_quality_indicators.len() < self.client.recipe_book().iter().len() {
|
|
state.update(|state| {
|
|
state.ids.recipe_list_quality_indicators.resize(
|
|
self.client.recipe_book().iter().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
});
|
|
}
|
|
if state.ids.recipe_list_materials_indicators.len() < self.client.recipe_book().iter().len()
|
|
{
|
|
state.update(|state| {
|
|
state.ids.recipe_list_materials_indicators.resize(
|
|
self.client.recipe_book().iter().len(),
|
|
&mut ui.widget_id_generator(),
|
|
)
|
|
});
|
|
}
|
|
for (i, (name, recipe, is_craftable, has_materials)) in ordered_recipes
|
|
.into_iter()
|
|
.filter(|(_, recipe, _, _)| self.show.crafting_tab.satisfies(recipe))
|
|
.enumerate()
|
|
{
|
|
let button = Button::image(if state.selected_recipe.as_ref() == Some(name) {
|
|
self.imgs.selection
|
|
} else {
|
|
self.imgs.nothing
|
|
})
|
|
.and(|button| {
|
|
if i == 0 {
|
|
button.top_left_with_margins_on(state.ids.align_rec, 2.0, 7.0)
|
|
} else {
|
|
button.down_from(state.ids.recipe_list_btns[i - 1], 5.0)
|
|
}
|
|
})
|
|
.w(171.0)
|
|
.hover_image(self.imgs.selection_hover)
|
|
.press_image(self.imgs.selection_press)
|
|
.image_color(color::rgba(1.0, 0.82, 0.27, 1.0));
|
|
|
|
let text = Text::new(recipe.output.0.name())
|
|
.color(if is_craftable {
|
|
TEXT_COLOR
|
|
} else {
|
|
TEXT_GRAY_COLOR
|
|
})
|
|
.font_size(self.fonts.cyri.scale(12))
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.w(163.0)
|
|
.mid_top_with_margin_on(state.ids.recipe_list_btns[i], 3.0)
|
|
.graphics_for(state.ids.recipe_list_btns[i])
|
|
.center_justify();
|
|
|
|
let text_height = match text.get_y_dimension(ui) {
|
|
Dimension::Absolute(y) => y,
|
|
_ => 0.0,
|
|
};
|
|
let button_height = (text_height + 7.0).max(20.0);
|
|
|
|
if button
|
|
.h(button_height)
|
|
.set(state.ids.recipe_list_btns[i], ui)
|
|
.was_clicked()
|
|
{
|
|
if state.selected_recipe.as_ref() == Some(name) {
|
|
state.update(|s| s.selected_recipe = None);
|
|
} else {
|
|
state.update(|s| s.selected_recipe = Some(name.clone()));
|
|
}
|
|
}
|
|
// set the text here so that the correct position of the button is retrieved
|
|
text.set(state.ids.recipe_list_labels[i], ui);
|
|
|
|
// Sidebar color
|
|
let color::Hsla(h, s, l, _) = get_quality_col(recipe.output.0.as_ref()).to_hsl();
|
|
let val_multiplier = if is_craftable { 0.7 } else { 0.5 };
|
|
// Apply conversion to hsv, multiply v by the desired amount, then revert to
|
|
// hsl. Conversion formulae: https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion
|
|
// Note that division by 0 is not possible since none of the colours are black
|
|
// or white
|
|
let quality_col = color::hsl(
|
|
h,
|
|
s * val_multiplier * f32::min(l, 1.0 - l)
|
|
/ f32::min(l * val_multiplier, 1.0 - l * val_multiplier),
|
|
l * val_multiplier,
|
|
);
|
|
|
|
Button::image(self.imgs.quality_indicator)
|
|
.image_color(quality_col)
|
|
.w_h(4.0, button_height)
|
|
.left_from(state.ids.recipe_list_btns[i], 1.0)
|
|
.graphics_for(state.ids.recipe_list_btns[i])
|
|
.set(state.ids.recipe_list_quality_indicators[i], ui);
|
|
|
|
// Sidebar crafting tool icon
|
|
if has_materials && !is_craftable {
|
|
let station_img = match recipe.craft_sprite {
|
|
Some(SpriteKind::Anvil) => Some("Anvil"),
|
|
Some(SpriteKind::Cauldron) => Some("Cauldron"),
|
|
Some(SpriteKind::CookingPot) => Some("CookingPot"),
|
|
Some(SpriteKind::CraftingBench) => Some("CraftingBench"),
|
|
Some(SpriteKind::Forge) => Some("Forge"),
|
|
Some(SpriteKind::Loom) => Some("Loom"),
|
|
Some(SpriteKind::SpinningWheel) => Some("SpinningWheel"),
|
|
Some(SpriteKind::TanningRack) => Some("TanningRack"),
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(station_img_str) = station_img {
|
|
Button::image(animate_by_pulse(
|
|
&self
|
|
.item_imgs
|
|
.img_ids_or_not_found_img(Tool(station_img_str.to_string())),
|
|
self.pulse,
|
|
))
|
|
.image_color(color::LIGHT_RED)
|
|
.w_h(button_height - 8.0, button_height - 8.0)
|
|
.top_left_with_margins_on(state.ids.recipe_list_btns[i], 4.0, 4.0)
|
|
.graphics_for(state.ids.recipe_list_btns[i])
|
|
.set(state.ids.recipe_list_materials_indicators[i], ui);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Selected Recipe
|
|
if let Some((recipe_name, recipe)) = state
|
|
.selected_recipe
|
|
.as_ref()
|
|
.and_then(|rn| self.client.recipe_book().get(rn.as_str()).map(|r| (rn, r)))
|
|
{
|
|
// Title
|
|
Text::new(recipe.output.0.name())
|
|
.mid_top_with_margin_on(state.ids.align_ing, -22.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(14))
|
|
.color(TEXT_COLOR)
|
|
.parent(state.ids.window)
|
|
.set(state.ids.title_ing, ui);
|
|
let can_perform = self
|
|
.client
|
|
.available_recipes()
|
|
.get(recipe_name.as_str())
|
|
.map_or(false, |cs| {
|
|
cs.map_or(true, |cs| {
|
|
Some(cs) == self.show.craft_sprite.map(|(_, s)| s)
|
|
})
|
|
});
|
|
|
|
// Craft button
|
|
if Button::image(self.imgs.button)
|
|
.w_h(105.0, 25.0)
|
|
.hover_image(
|
|
can_perform
|
|
.then_some(self.imgs.button_hover)
|
|
.unwrap_or(self.imgs.button),
|
|
)
|
|
.press_image(
|
|
can_perform
|
|
.then_some(self.imgs.button_press)
|
|
.unwrap_or(self.imgs.button),
|
|
)
|
|
.label(self.localized_strings.get("hud.crafting.craft"))
|
|
.label_y(conrod_core::position::Relative::Scalar(1.0))
|
|
.label_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
|
|
.label_font_size(self.fonts.cyri.scale(12))
|
|
.label_font_id(self.fonts.cyri.conrod_id)
|
|
.image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
|
|
.mid_bottom_with_margin_on(state.ids.align_ing, -31.0)
|
|
.parent(state.ids.window_frame)
|
|
.set(state.ids.btn_craft, ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::CraftRecipe(recipe_name.clone()));
|
|
}
|
|
|
|
// Output Image Frame
|
|
let quality_col_img = match recipe.output.0.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(60.0, 60.0)
|
|
.top_right_with_margins_on(state.ids.align_ing, 15.0, 10.0)
|
|
.parent(state.ids.align_ing)
|
|
.set(state.ids.output_img_frame, ui);
|
|
|
|
let output_text = format!("x{}", &recipe.output.1.to_string());
|
|
// Output Image
|
|
Button::image(animate_by_pulse(
|
|
&self
|
|
.item_imgs
|
|
.img_ids_or_not_found_img((&*recipe.output.0).into()),
|
|
self.pulse,
|
|
))
|
|
.w_h(55.0, 55.0)
|
|
.label(&output_text)
|
|
.label_color(TEXT_COLOR)
|
|
.label_font_size(self.fonts.cyri.scale(14))
|
|
.label_font_id(self.fonts.cyri.conrod_id)
|
|
.label_y(conrod_core::position::Relative::Scalar(-24.0))
|
|
.label_x(conrod_core::position::Relative::Scalar(24.0))
|
|
.middle_of(state.ids.output_img_frame)
|
|
.with_item_tooltip(
|
|
self.item_tooltip_manager,
|
|
&*recipe.output.0,
|
|
&None,
|
|
&item_tooltip,
|
|
)
|
|
.set(state.ids.output_img, ui);
|
|
|
|
// Tags
|
|
if state.ids.tags_ing.len() < CraftingTab::iter().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.tags_ing
|
|
.resize(CraftingTab::iter().len(), &mut ui.widget_id_generator())
|
|
});
|
|
}
|
|
for (row, chunk) in CraftingTab::iter()
|
|
.filter(|crafting_tab| match crafting_tab {
|
|
CraftingTab::All => false,
|
|
_ => crafting_tab.satisfies(recipe),
|
|
})
|
|
.filter(|crafting_tab| crafting_tab != &self.show.crafting_tab)
|
|
.collect::<Vec<_>>()
|
|
.chunks(3)
|
|
.enumerate()
|
|
{
|
|
for (col, crafting_tab) in chunk.iter().rev().enumerate() {
|
|
let i = 3 * row + col;
|
|
let icon = Image::new(crafting_tab.img_id(self.imgs))
|
|
.w_h(20.0, 20.0)
|
|
.parent(state.ids.window);
|
|
let icon = if col == 0 {
|
|
icon.bottom_right_with_margins_on(
|
|
state.ids.output_img_frame,
|
|
-24.0 - 24.0 * (row as f64),
|
|
4.0,
|
|
)
|
|
} else {
|
|
icon.left_from(state.ids.tags_ing[i - 1], 4.0)
|
|
};
|
|
icon.with_tooltip(
|
|
self.tooltip_manager,
|
|
self.localized_strings.get(crafting_tab.name_key()),
|
|
"",
|
|
&tabs_tooltip,
|
|
TEXT_COLOR,
|
|
)
|
|
.set(state.ids.tags_ing[i], ui);
|
|
}
|
|
}
|
|
// Crafting Station Info
|
|
if recipe.craft_sprite.is_some() {
|
|
Text::new(
|
|
self.localized_strings
|
|
.get("hud.crafting.req_crafting_station"),
|
|
)
|
|
.top_left_with_margins_on(state.ids.align_ing, 10.0, 5.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(18))
|
|
.color(TEXT_COLOR)
|
|
.set(state.ids.req_station_title, ui);
|
|
let station_img = match recipe.craft_sprite {
|
|
Some(SpriteKind::Anvil) => "Anvil",
|
|
Some(SpriteKind::Cauldron) => "Cauldron",
|
|
Some(SpriteKind::CookingPot) => "CookingPot",
|
|
Some(SpriteKind::CraftingBench) => "CraftingBench",
|
|
Some(SpriteKind::Forge) => "Forge",
|
|
Some(SpriteKind::Loom) => "Loom",
|
|
Some(SpriteKind::SpinningWheel) => "SpinningWheel",
|
|
Some(SpriteKind::TanningRack) => "TanningRack",
|
|
None => "CraftsmanHammer",
|
|
_ => "CraftsmanHammer",
|
|
};
|
|
Image::new(animate_by_pulse(
|
|
&self
|
|
.item_imgs
|
|
.img_ids_or_not_found_img(Tool(station_img.to_string())),
|
|
self.pulse,
|
|
))
|
|
.w_h(25.0, 25.0)
|
|
.down_from(state.ids.req_station_title, 10.0)
|
|
.parent(state.ids.align_ing)
|
|
.set(state.ids.req_station_img, ui);
|
|
|
|
let station_name = match recipe.craft_sprite {
|
|
Some(SpriteKind::Anvil) => "hud.crafting.anvil",
|
|
Some(SpriteKind::Cauldron) => "hud.crafting.cauldron",
|
|
Some(SpriteKind::CookingPot) => "hud.crafting.cooking_pot",
|
|
Some(SpriteKind::CraftingBench) => "hud.crafting.crafting_bench",
|
|
Some(SpriteKind::Forge) => "hud.crafting.forge",
|
|
Some(SpriteKind::Loom) => "hud.crafting.loom",
|
|
Some(SpriteKind::SpinningWheel) => "hud.crafting.spinning_wheel",
|
|
Some(SpriteKind::TanningRack) => "hud.crafting.tanning_rack",
|
|
_ => "",
|
|
};
|
|
Text::new(self.localized_strings.get(station_name))
|
|
.right_from(state.ids.req_station_img, 10.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(14))
|
|
.color(
|
|
if self.show.craft_sprite.map(|(_, s)| s) == recipe.craft_sprite {
|
|
TEXT_COLOR
|
|
} else {
|
|
TEXT_DULL_RED_COLOR
|
|
},
|
|
)
|
|
.set(state.ids.req_station_txt, ui);
|
|
}
|
|
// Ingredients Text
|
|
let mut ing_txt = Text::new(self.localized_strings.get("hud.crafting.ingredients"))
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(18))
|
|
.color(TEXT_COLOR);
|
|
if recipe.craft_sprite.is_some() {
|
|
ing_txt = ing_txt.down_from(state.ids.req_station_img, 10.0);
|
|
} else {
|
|
ing_txt = ing_txt.top_left_with_margins_on(state.ids.align_ing, 10.0, 5.0);
|
|
};
|
|
ing_txt.set(state.ids.ingredients_txt, ui);
|
|
|
|
// Ingredient images with tooltip
|
|
if state.ids.ingredient_frame.len() < recipe.inputs().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.ingredient_frame
|
|
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
|
|
});
|
|
};
|
|
if state.ids.ingredients.len() < recipe.inputs().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.ingredients
|
|
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
|
|
});
|
|
};
|
|
if state.ids.ingredient_img.len() < recipe.inputs().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.ingredient_img
|
|
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
|
|
});
|
|
};
|
|
if state.ids.req_text.len() < recipe.inputs().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.req_text
|
|
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
|
|
});
|
|
};
|
|
|
|
// Widget generation for every ingredient
|
|
for (i, (recipe_input, amount)) in recipe.inputs.iter().enumerate() {
|
|
let item_def = match recipe_input {
|
|
RecipeInput::Item(item_def) => Arc::clone(item_def),
|
|
RecipeInput::Tag(tag) => Arc::<ItemDef>::load_expect_cloned(
|
|
&self
|
|
.inventory
|
|
.slots()
|
|
.filter_map(|slot| {
|
|
slot.as_ref().and_then(|item| {
|
|
if item.matches_recipe_input(recipe_input) {
|
|
Some(item.item_definition_id().to_string())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
})
|
|
.next()
|
|
.unwrap_or_else(|| tag.exemplar_identifier().to_string()),
|
|
),
|
|
};
|
|
|
|
// Grey color for images and text if their amount is too low to craft the item
|
|
let item_count_in_inventory = self.inventory.item_count(&*item_def);
|
|
let col = if item_count_in_inventory >= u64::from(*amount.max(&1)) {
|
|
TEXT_COLOR
|
|
} else {
|
|
TEXT_DULL_RED_COLOR
|
|
};
|
|
// Slot BG
|
|
let frame_pos = if i == 0 {
|
|
state.ids.ingredients_txt
|
|
} else {
|
|
state.ids.ingredient_frame[i - 1]
|
|
};
|
|
// add a larger offset for the the first ingredient and the "Required Text for
|
|
// Catalysts/Tools"
|
|
let frame_offset = if i == 0 {
|
|
10.0
|
|
} else if *amount == 0 {
|
|
5.0
|
|
} else {
|
|
0.0
|
|
};
|
|
let quality_col_img = match &item_def.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,
|
|
};
|
|
let frame = Image::new(quality_col_img).w_h(25.0, 25.0);
|
|
let frame = if *amount == 0 {
|
|
frame.down_from(state.ids.req_text[i], 10.0 + frame_offset)
|
|
} else {
|
|
frame.down_from(frame_pos, 10.0 + frame_offset)
|
|
};
|
|
frame.set(state.ids.ingredient_frame[i], ui);
|
|
//Item Image
|
|
Button::image(animate_by_pulse(
|
|
&self.item_imgs.img_ids_or_not_found_img((&*item_def).into()),
|
|
self.pulse,
|
|
))
|
|
.w_h(22.0, 22.0)
|
|
.middle_of(state.ids.ingredient_frame[i])
|
|
.with_item_tooltip(self.item_tooltip_manager, &*item_def, &None, &item_tooltip)
|
|
.set(state.ids.ingredient_img[i], ui);
|
|
// Ingredients text and amount
|
|
// Don't show inventory amounts above 999 to avoid the widget clipping
|
|
let over9k = "99+";
|
|
let in_inv: &str = &item_count_in_inventory.to_string();
|
|
// Show Ingredients
|
|
// Align "Required" Text below last ingredient
|
|
if *amount == 0 {
|
|
// Catalysts/Tools
|
|
Text::new(self.localized_strings.get("hud.crafting.tool_cata"))
|
|
.down_from(state.ids.ingredient_frame[i - 1], 20.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(14))
|
|
.color(TEXT_COLOR)
|
|
.set(state.ids.req_text[i], ui);
|
|
Text::new(item_def.name())
|
|
.right_from(state.ids.ingredient_frame[i], 10.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(14))
|
|
.color(col)
|
|
.set(state.ids.ingredients[i], ui);
|
|
} else {
|
|
// Ingredients
|
|
let name = match recipe_input {
|
|
RecipeInput::Item(_) => item_def.name().to_string(),
|
|
RecipeInput::Tag(tag) => format!("Any {} item", tag.name()),
|
|
};
|
|
let input = format!(
|
|
"{}x {} ({})",
|
|
amount,
|
|
name,
|
|
if item_count_in_inventory > 99 {
|
|
over9k
|
|
} else {
|
|
in_inv
|
|
}
|
|
);
|
|
// Ingredient Text
|
|
Text::new(&input)
|
|
.right_from(state.ids.ingredient_frame[i], 10.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(12))
|
|
.color(col)
|
|
.set(state.ids.ingredients[i], ui);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search / Title Recipes
|
|
if let Some(key) = &self.show.crafting_search_key {
|
|
if Button::image(self.imgs.close_btn)
|
|
.top_left_with_margins_on(state.ids.align_rec, -20.0, 5.0)
|
|
.w_h(14.0, 14.0)
|
|
.hover_image(self.imgs.close_btn_hover)
|
|
.press_image(self.imgs.close_btn_press)
|
|
.parent(state.ids.window)
|
|
.set(state.ids.btn_close_search, ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::SearchRecipe(None));
|
|
}
|
|
Rectangle::fill([162.0, 20.0])
|
|
.top_left_with_margins_on(state.ids.btn_close_search, -2.0, 16.0)
|
|
.hsla(0.0, 0.0, 0.0, 0.7)
|
|
.depth(1.0)
|
|
.parent(state.ids.window)
|
|
.set(state.ids.input_bg_search, ui);
|
|
if let Some(string) = TextEdit::new(key.as_str())
|
|
.top_left_with_margins_on(state.ids.btn_close_search, -2.0, 18.0)
|
|
.w_h(138.0, 20.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(14))
|
|
.color(TEXT_COLOR)
|
|
.parent(state.ids.window)
|
|
.set(state.ids.input_search, ui)
|
|
{
|
|
events.push(Event::SearchRecipe(Some(string)));
|
|
}
|
|
} else {
|
|
Text::new(self.localized_strings.get("hud.crafting.recipes"))
|
|
.mid_top_with_margin_on(state.ids.align_rec, -22.0)
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.font_size(self.fonts.cyri.scale(14))
|
|
.color(TEXT_COLOR)
|
|
.parent(state.ids.window)
|
|
.set(state.ids.title_rec, ui);
|
|
Rectangle::fill_with([148.0, 20.0], color::TRANSPARENT)
|
|
.top_left_with_margins_on(state.ids.window, 52.0, 26.0)
|
|
.graphics_for(state.ids.btn_open_search)
|
|
.set(state.ids.input_overlay_search, ui);
|
|
if Button::image(self.imgs.search_btn)
|
|
.top_left_with_margins_on(state.ids.align_rec, -21.0, 5.0)
|
|
.w_h(16.0, 16.0)
|
|
.hover_image(self.imgs.search_btn_hover)
|
|
.press_image(self.imgs.search_btn_press)
|
|
.parent(state.ids.window)
|
|
.set(state.ids.btn_open_search, ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::SearchRecipe(Some(String::new())));
|
|
events.push(Event::Focus(state.ids.input_search));
|
|
}
|
|
}
|
|
// Scrollbars
|
|
Scrollbar::y_axis(state.ids.align_rec)
|
|
.thickness(5.0)
|
|
.rgba(0.33, 0.33, 0.33, 1.0)
|
|
.set(state.ids.scrollbar_rec, ui);
|
|
Scrollbar::y_axis(state.ids.align_ing)
|
|
.thickness(5.0)
|
|
.rgba(0.33, 0.33, 0.33, 1.0)
|
|
.set(state.ids.scrollbar_ing, ui);
|
|
|
|
events
|
|
}
|
|
}
|