Widened recipe list in crafting menu and added quality indicators to names in the list

This commit is contained in:
hqurve 2021-06-06 15:55:11 +00:00 committed by Marcel
parent 85b8d4e7c0
commit 39fc48ac17
10 changed files with 224 additions and 78 deletions

View File

@ -67,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add chest to each dungeon with unique loot - Add chest to each dungeon with unique loot
- Added a new option in the graphics menu to enable GPU timing (not always supported). The timing values can be viewed in the HUD debug info (F3) and will be saved as chrome trace files in the working directory when taking a screenshot. - Added a new option in the graphics menu to enable GPU timing (not always supported). The timing values can be viewed in the HUD debug info (F3) and will be saved as chrome trace files in the working directory when taking a screenshot.
- Added new Present Mode option in the graphics menu. Selecting Fifo (i.e. vsync) or Mailbox can be used to eliminate screen tearing. - Added new Present Mode option in the graphics menu. Selecting Fifo (i.e. vsync) or Mailbox can be used to eliminate screen tearing.
- Quality color indicators next to recipe names in crafting menu
### Changed ### Changed
@ -121,6 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moved the rest of screenshot work into the background. Screenshoting no longer induces large pauses. - Moved the rest of screenshot work into the background. Screenshoting no longer induces large pauses.
- Reworked tidal warrior to have unique attacks - Reworked tidal warrior to have unique attacks
- Reworked yeti to have unique attacks - Reworked yeti to have unique attacks
- Widened recipe name list in crafting menu
### Removed ### Removed

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

View File

@ -1,4 +1,5 @@
use super::{ use super::{
get_quality_col,
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::{animate_by_pulse, ItemImgs, ItemKey::Tool}, item_imgs::{animate_by_pulse, ItemImgs, ItemKey::Tool},
Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN, Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
@ -24,6 +25,7 @@ use common::{
}; };
use conrod_core::{ use conrod_core::{
color, image, color, image,
position::Dimension,
widget::{self, Button, Image, Rectangle, Scrollbar, Text, TextEdit}, widget::{self, Button, Image, Rectangle, Scrollbar, Text, TextEdit},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
@ -52,7 +54,9 @@ widget_ids! {
align_ing, align_ing,
scrollbar_ing, scrollbar_ing,
btn_craft, btn_craft,
recipe_names[], recipe_list_btns[],
recipe_list_labels[],
recipe_list_quality_indicators[],
recipe_img_frame[], recipe_img_frame[],
recipe_img[], recipe_img[],
ingredients[], ingredients[],
@ -274,13 +278,13 @@ impl<'a> Widget for Crafting<'a> {
Image::new(self.imgs.crafting_window) Image::new(self.imgs.crafting_window)
.bottom_right_with_margins_on(ui.window, 308.0, 450.0) .bottom_right_with_margins_on(ui.window, 308.0, 450.0)
.color(Some(UI_MAIN)) .color(Some(UI_MAIN))
.w_h(422.0, 460.0) .w_h(456.0, 460.0)
.set(state.ids.window, ui); .set(state.ids.window, ui);
// Window // Window
Image::new(self.imgs.crafting_frame) Image::new(self.imgs.crafting_frame)
.middle_of(state.ids.window) .middle_of(state.ids.window)
.color(Some(UI_HIGHLIGHT_0)) .color(Some(UI_HIGHLIGHT_0))
.w_h(422.0, 460.0) .wh_of(state.ids.window)
.set(state.ids.window_frame, ui); .set(state.ids.window_frame, ui);
// Crafting Icon // Crafting Icon
@ -310,7 +314,7 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.title_main, ui); .set(state.ids.title_main, ui);
// Alignment // Alignment
Rectangle::fill_with([136.0, 378.0], color::TRANSPARENT) Rectangle::fill_with([170.0, 378.0], color::TRANSPARENT)
.top_left_with_margins_on(state.ids.window_frame, 74.0, 5.0) .top_left_with_margins_on(state.ids.window_frame, 74.0, 5.0)
.scroll_kids_vertically() .scroll_kids_vertically()
.set(state.ids.align_rec, ui); .set(state.ids.align_rec, ui);
@ -398,12 +402,6 @@ impl<'a> Widget for Crafting<'a> {
// First available recipes, then unavailable ones, each alphabetically // First available recipes, then unavailable ones, each alphabetically
// In the triples, "name" is the recipe book key, and "recipe.output.0.name()" // In the triples, "name" is the recipe book key, and "recipe.output.0.name()"
// is the display name (as stored in the item descriptors) // is the display name (as stored in the item descriptors)
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum RecipeIngredientQuantity {
All,
Some,
None,
}
let mut ordered_recipes: Vec<_> = self let mut ordered_recipes: Vec<_> = self
.client .client
.recipe_book() .recipe_book()
@ -420,15 +418,7 @@ impl<'a> Widget for Crafting<'a> {
} }
}) })
.map(|(name, recipe)| { .map(|(name, recipe)| {
let at_least_some_ingredients = recipe.inputs.iter().any(|(input, amount)| { let is_craftable =
*amount > 0
&& self.inventory.slots().any(|slot| {
slot.as_ref()
.map(|item| item.matches_recipe_input(input))
.unwrap_or(false)
})
});
let show_craft_sprite =
self.client self.client
.available_recipes() .available_recipes()
.get(name.as_str()) .get(name.as_str())
@ -437,81 +427,112 @@ impl<'a> Widget for Crafting<'a> {
Some(cs) == self.show.craft_sprite.map(|(_, s)| s) Some(cs) == self.show.craft_sprite.map(|(_, s)| s)
}) })
}); });
let state = if show_craft_sprite { (name, recipe, is_craftable)
RecipeIngredientQuantity::All
} else if at_least_some_ingredients {
RecipeIngredientQuantity::Some
} else {
RecipeIngredientQuantity::None
};
(name, recipe, state)
}) })
.collect(); .collect();
ordered_recipes.sort_by_key(|(_, recipe, state)| (*state, recipe.output.0.name())); ordered_recipes.sort_by_key(|(_, recipe, state)| {
(!state, recipe.output.0.quality(), recipe.output.0.name())
});
// Recipe list // Recipe list
if state.ids.recipe_names.len() < self.client.recipe_book().iter().len() { if state.ids.recipe_list_btns.len() < self.client.recipe_book().iter().len() {
state.update(|state| { state.update(|state| {
state.ids.recipe_names.resize( state.ids.recipe_list_btns.resize(
self.client.recipe_book().iter().len(), self.client.recipe_book().iter().len(),
&mut ui.widget_id_generator(), &mut ui.widget_id_generator(),
) )
}); });
} }
for (i, (name, recipe, quantity)) in ordered_recipes 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(),
)
});
}
for (i, (name, recipe, is_craftable)) in ordered_recipes
.into_iter() .into_iter()
.filter(|(_, recipe, _)| self.show.crafting_tab.satisfies(recipe.output.0.as_ref())) .filter(|(_, recipe, _)| self.show.crafting_tab.satisfies(recipe.output.0.as_ref()))
.enumerate() .enumerate()
{ {
let button = Button::image( let button = Button::image(if state.selected_recipe.as_ref() == Some(name) {
if state self.imgs.selection
.selected_recipe
.as_ref()
.map(|s| s != name)
.unwrap_or(false)
{
self.imgs.nothing
} else {
match state.selected_recipe {
None => self.imgs.nothing,
Some(_) => self.imgs.selection,
}
},
);
// Recipe Button
let button = if i == 0 {
button.mid_top_with_margin_on(state.ids.align_rec, 2.0)
} else { } else {
button.mid_bottom_with_margin_on(state.ids.recipe_names[i - 1], -25.0) self.imgs.nothing
}; })
let text_color = match quantity { .and(|button| {
RecipeIngredientQuantity::All => TEXT_COLOR, if i == 0 {
RecipeIngredientQuantity::Some => TEXT_GRAY_COLOR, button.top_left_with_margins_on(state.ids.align_rec, 2.0, 7.0)
RecipeIngredientQuantity::None => TEXT_DULL_RED_COLOR, } else {
button.down_from(state.ids.recipe_list_btns[i - 1], 5.0)
}
})
.w(157.0)
.hover_image(self.imgs.selection_hover)
.press_image(self.imgs.selection_press);
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(149.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,
}; };
if button if button
.label(recipe.output.0.name()) .h((text_height + 7.0).max(20.0))
.w_h(130.0, 20.0) .set(state.ids.recipe_list_btns[i], ui)
.hover_image(self.imgs.selection_hover)
.press_image(self.imgs.selection_press)
.label_color(text_color)
.label_font_size(self.fonts.cyri.scale(12))
.label_font_id(self.fonts.cyri.conrod_id)
.label_y(conrod_core::position::Relative::Scalar(2.0))
.set(state.ids.recipe_names[i], ui)
.was_clicked() .was_clicked()
{ {
if state if state.selected_recipe.as_ref() == Some(name) {
.selected_recipe
.as_ref()
.map(|s| s == name)
.unwrap_or(false)
{
state.update(|s| s.selected_recipe = None); state.update(|s| s.selected_recipe = None);
} else { } else {
state.update(|s| s.selected_recipe = Some(name.clone())); 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)
.h_of(state.ids.recipe_list_btns[i])
.w(4.0)
.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);
} }
// Selected Recipe // Selected Recipe
@ -877,7 +898,7 @@ impl<'a> Widget for Crafting<'a> {
{ {
events.push(Event::SearchRecipe(None)); events.push(Event::SearchRecipe(None));
} }
Rectangle::fill([114.0, 20.0]) Rectangle::fill([148.0, 20.0])
.top_left_with_margins_on(state.ids.btn_close_search, -2.0, 16.0) .top_left_with_margins_on(state.ids.btn_close_search, -2.0, 16.0)
.hsla(0.0, 0.0, 0.0, 0.7) .hsla(0.0, 0.0, 0.0, 0.7)
.depth(1.0) .depth(1.0)
@ -885,7 +906,7 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.input_bg_search, ui); .set(state.ids.input_bg_search, ui);
if let Some(string) = TextEdit::new(key.as_str()) if let Some(string) = TextEdit::new(key.as_str())
.top_left_with_margins_on(state.ids.btn_close_search, -2.0, 18.0) .top_left_with_margins_on(state.ids.btn_close_search, -2.0, 18.0)
.w_h(90.0, 20.0) .w_h(124.0, 20.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -902,7 +923,7 @@ impl<'a> Widget for Crafting<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.parent(state.ids.window) .parent(state.ids.window)
.set(state.ids.title_rec, ui); .set(state.ids.title_rec, ui);
Rectangle::fill_with([114.0, 20.0], color::TRANSPARENT) Rectangle::fill_with([148.0, 20.0], color::TRANSPARENT)
.top_left_with_margins_on(state.ids.window, 52.0, 26.0) .top_left_with_margins_on(state.ids.window, 52.0, 26.0)
.graphics_for(state.ids.btn_open_search) .graphics_for(state.ids.btn_open_search)
.set(state.ids.input_overlay_search, ui); .set(state.ids.input_overlay_search, ui);

View File

@ -96,6 +96,7 @@ image_ids! {
crafting_icon: "voxygen.element.ui.generic.buttons.anvil", crafting_icon: "voxygen.element.ui.generic.buttons.anvil",
crafting_icon_hover: "voxygen.element.ui.generic.buttons.anvil_hover", crafting_icon_hover: "voxygen.element.ui.generic.buttons.anvil_hover",
crafting_icon_press: "voxygen.element.ui.generic.buttons.anvil_press", crafting_icon_press: "voxygen.element.ui.generic.buttons.anvil_press",
quality_indicator: "voxygen.element.ui.crafting.quality_indicator",
icon_armor: "voxygen.element.ui.crafting.icons.armors", icon_armor: "voxygen.element.ui.crafting.icons.armors",
icon_tools: "voxygen.element.ui.crafting.icons.crafting_tools", icon_tools: "voxygen.element.ui.crafting.icons.crafting_tools",
icon_dismantle: "voxygen.element.ui.crafting.icons.dismantle", icon_dismantle: "voxygen.element.ui.crafting.icons.dismantle",

View File

@ -135,7 +135,7 @@ const DEBUFF_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0);
// Item Quality Colors // Item Quality Colors
const QUALITY_LOW: Color = Color::Rgba(0.41, 0.41, 0.41, 1.0); // Grey - Trash, can be sold to vendors const QUALITY_LOW: Color = Color::Rgba(0.41, 0.41, 0.41, 1.0); // Grey - Trash, can be sold to vendors
const QUALITY_COMMON: Color = Color::Rgba(0.79, 1.09, 1.09, 1.0); // No Color - Crafting mats, food, starting equipment, quest items (like keys), rewards for easy quests const QUALITY_COMMON: Color = Color::Rgba(0.79, 1.00, 1.00, 1.0); // No Color - Crafting mats, food, starting equipment, quest items (like keys), rewards for easy quests
const QUALITY_MODERATE: Color = Color::Rgba(0.06, 0.69, 0.12, 1.0); // Green - Quest Rewards, commonly looted items from NPCs const QUALITY_MODERATE: Color = Color::Rgba(0.06, 0.69, 0.12, 1.0); // Green - Quest Rewards, commonly looted items from NPCs
const QUALITY_HIGH: Color = Color::Rgba(0.18, 0.32, 0.9, 1.0); // Blue - Dungeon rewards, boss loot, rewards for hard quests const QUALITY_HIGH: Color = Color::Rgba(0.18, 0.32, 0.9, 1.0); // Blue - Dungeon rewards, boss loot, rewards for hard quests
const QUALITY_EPIC: Color = Color::Rgba(0.58, 0.29, 0.93, 1.0); // Purple - Rewards for epic quests and very hard bosses const QUALITY_EPIC: Color = Color::Rgba(0.58, 0.29, 0.93, 1.0); // Purple - Rewards for epic quests and very hard bosses

View File

@ -19,6 +19,7 @@ pub use widgets::{
image_slider::ImageSlider, image_slider::ImageSlider,
ingame::{Ingame, Ingameable}, ingame::{Ingame, Ingameable},
item_tooltip::{ItemTooltip, ItemTooltipManager, ItemTooltipable}, item_tooltip::{ItemTooltip, ItemTooltipManager, ItemTooltipable},
outlined_text::OutlinedText,
radio_list::RadioList, radio_list::RadioList,
slot, slot,
toggle_button::ToggleButton, toggle_button::ToggleButton,

View File

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

View File

@ -0,0 +1,117 @@
use conrod_core::{
builder_methods, text,
widget::{self, Text},
widget_ids, Color, FontSize, Positionable, Sizeable, Widget, WidgetCommon,
};
#[derive(Clone, WidgetCommon)]
pub struct OutlinedText<'a> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
text: &'a str,
text_style: widget::text::Style,
outline_color: Option<Color>,
outline_width: f64,
}
widget_ids! {
struct Ids{
base,
outline1,
outline2,
outline3,
outline4,
}
}
pub struct State {
ids: Ids,
}
impl<'a> OutlinedText<'a> {
builder_methods! {
pub color {text_style.color = Some(Color)}
pub outline_color {outline_color = Some(Color)}
pub font_size {text_style.font_size = Some(FontSize)}
pub outline_width {outline_width = f64}
}
pub fn new(text: &'a str) -> Self {
Self {
common: widget::CommonBuilder::default(),
text,
text_style: widget::text::Style::default(),
outline_color: None,
outline_width: 0.0,
}
}
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
self.text_style.font_id = Some(Some(font_id));
self
}
}
impl<'a> Widget for OutlinedText<'a> {
type 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 {
id,
state,
ui,
rect,
..
} = args;
let mut outline_style = self.text_style;
outline_style.color = self.outline_color;
let shift = self.outline_width;
Text::new(self.text)
.with_style(self.text_style)
.xy(rect.xy())
.wh(rect.dim())
.parent(id)
.depth(-1.0)
.set(state.ids.base, ui);
Text::new(self.text)
.with_style(outline_style)
.x_y_relative_to(state.ids.base, shift, shift)
.wh_of(state.ids.base)
.set(state.ids.outline1, ui);
Text::new(self.text)
.with_style(outline_style)
.x_y_relative_to(state.ids.base, -shift, shift)
.wh_of(state.ids.base)
.set(state.ids.outline2, ui);
Text::new(self.text)
.with_style(outline_style)
.x_y_relative_to(state.ids.base, shift, -shift)
.wh_of(state.ids.base)
.set(state.ids.outline3, ui);
Text::new(self.text)
.with_style(outline_style)
.x_y_relative_to(state.ids.base, -shift, -shift)
.wh_of(state.ids.base)
.set(state.ids.outline4, ui);
}
}