diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index d7480366e8..b42bcacd77 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -6,17 +6,25 @@ use super::{ }; use crate::{ game_input::GameInput, - hud::{self, util}, - ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, + hud::{ + self, + slots::{AbilitySlot, SlotManager}, + util, + }, + ui::{ + fonts::Fonts, + slot::{ContentSize, SlotMaker}, + ImageFrame, Tooltip, TooltipManager, Tooltipable, + }, GlobalState, }; use conrod_core::{ color, image, - input::state::mouse::ButtonPosition, widget::{self, Button, Image, Rectangle, State, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, }; use i18n::Localization; +use vek::*; use client::{self, Client}; use common::{ @@ -40,7 +48,6 @@ use common::{ }, consts::{ENERGY_PER_LEVEL, HP_PER_LEVEL}, }; -use hashbrown::HashMap; use inline_tweak::*; use std::borrow::Cow; @@ -213,7 +220,6 @@ widget_ids! { ability_page_left, ability_page_right, active_abilities[], - active_abilities_bg[], active_abilities_keys[], main_weap_select, off_weap_select, @@ -248,6 +254,7 @@ pub struct Diary<'a> { localized_strings: &'a Localization, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut SlotManager, pulse: f32, #[conrod(common_builder)] @@ -261,7 +268,6 @@ pub struct Diary<'a> { pub struct DiaryShow { pub skilltreetab: SelectedSkillTree, pub section: DiarySection, - pub selected_ability: Option, } impl Default for DiaryShow { @@ -269,7 +275,6 @@ impl Default for DiaryShow { Self { skilltreetab: SelectedSkillTree::General, section: DiarySection::SkillTrees, - selected_ability: None, } } } @@ -294,6 +299,7 @@ impl<'a> Diary<'a> { localized_strings: &'a Localization, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut SlotManager, pulse: f32, ) -> Self { Self { @@ -314,6 +320,7 @@ impl<'a> Diary<'a> { localized_strings, rot_imgs, tooltip_manager, + slot_manager, pulse, common: widget::CommonBuilder::default(), created_btns_top_l: 0, @@ -347,8 +354,6 @@ pub enum Event { ChangeSkillTree(SelectedSkillTree), UnlockSkill(Skill), ChangeSection(DiarySection), - SelectAbility(Option), - ChangeAbility(usize, AuxiliaryAbility), } #[derive(PartialEq)] @@ -360,8 +365,6 @@ pub enum DiarySection { pub struct DiaryState { ids: Ids, - dragged_ability: Option, - id_ability_map: HashMap, ability_page: usize, } @@ -373,9 +376,6 @@ impl<'a> Widget for Diary<'a> { fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { DiaryState { ids: Ids::new(id_gen), - dragged_ability: None, - // Constructed during update sequence - id_ability_map: HashMap::new(), ability_page: 0, } } @@ -387,10 +387,6 @@ impl<'a> Widget for Diary<'a> { let widget::UpdateArgs { state, ui, .. } = args; let mut events = Vec::new(); - state.update(|s| { - s.id_ability_map.clear(); - }); - // Tooltips let diary_tooltip = Tooltip::new({ // Edge images [t, b, r, l] @@ -834,6 +830,7 @@ impl<'a> Widget for Diary<'a> { }, DiarySection::AbilitySelection => { use comp::ability::AbilityInput; + // Background Art Image::new(self.imgs.book_bg) .w_h(299.0 * 4.0, 184.0 * 4.0) @@ -852,22 +849,38 @@ impl<'a> Widget for Diary<'a> { .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0) .set(state.ids.sb_page_right_align, ui); - // Display all active abilities on right of window + // Display all active abilities on bottom of window state.update(|s| { s.ids .active_abilities .resize(MAX_ABILITIES, &mut ui.widget_id_generator()) }); - state.update(|s| { - s.ids - .active_abilities_bg - .resize(MAX_ABILITIES, &mut ui.widget_id_generator()) - }); state.update(|s| { s.ids .active_abilities_keys .resize(MAX_ABILITIES, &mut ui.widget_id_generator()) }); + + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: tweak!(0.9), + }, + selected_content_scale: 1.067, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: &(self.active_abilities, self.inventory, self.skill_set), + image_source: self.imgs, + slot_manager: Some(self.slot_manager), + pulse: 0.0, + }; + for i in 0..MAX_ABILITIES { let ability_id = self .active_abilities @@ -877,11 +890,15 @@ impl<'a> Widget for Diary<'a> { Some(self.skill_set), ) .ability_id(Some(self.inventory)); + let (ability_title, ability_desc) = + util::ability_description(ability_id.unwrap_or("")); let image_size = tweak!(80.0); let image_offsets = tweak!(92.0) * i as f64; - let mut ability_slot = - Image::new(self.imgs.inv_slot).w_h(image_size, image_size); + + let slot = AbilitySlot::Slot(i); + let mut ability_slot = slot_maker.fabricate(slot, [image_size; 2]); + if i == 0 { ability_slot = ability_slot.top_left_with_margins_on( state.ids.spellbook_skills_bg, @@ -889,17 +906,10 @@ impl<'a> Widget for Diary<'a> { tweak!(32.0) + image_offsets, ); } else { - ability_slot = ability_slot - .right_from(state.ids.active_abilities_bg[i - 1], tweak!(4.0)) + ability_slot = + ability_slot.right_from(state.ids.active_abilities[i - 1], 4.0) } - ability_slot.set(state.ids.active_abilities_bg[i], ui); - let ability_image = ability_id - .map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)); - let (ability_title, ability_desc) = - util::ability_description(ability_id.unwrap_or("")); - if Button::image(ability_image) - .w_h(image_size * tweak!(0.9), image_size * tweak!(0.9)) - .middle_of(state.ids.active_abilities_bg[i]) + ability_slot .with_tooltip( self.tooltip_manager, ability_title, @@ -907,18 +917,8 @@ impl<'a> Widget for Diary<'a> { &diary_tooltip, TEXT_COLOR, ) - .set(state.ids.active_abilities[i], ui) - .was_clicked() - { - events.push(Event::ChangeAbility( - i, - self.show - .diary_fields - .selected_ability - .unwrap_or(AuxiliaryAbility::Empty), - )); - events.push(Event::SelectAbility(None)); - } + .set(state.ids.active_abilities[i], ui); + // Display Slot Keybinding let keys = &self.global_state.settings.controls; let key_layout = &self.global_state.window.key_layout; @@ -1090,6 +1090,26 @@ impl<'a> Widget for Diary<'a> { let ability_start = state.ability_page * ABILITIES_PER_PAGE; let abilities_range = ability_start..(ability_start + ABILITIES_PER_PAGE); + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: tweak!(1.0), + }, + selected_content_scale: tweak!(1.067), + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: &(self.active_abilities, self.inventory, self.skill_set), + image_source: self.imgs, + slot_manager: Some(self.slot_manager), + pulse: 0.0, + }; + for (id_index, (ability_id, ability)) in abilities .iter() .enumerate() @@ -1098,26 +1118,15 @@ impl<'a> Widget for Diary<'a> { }) .enumerate() { - let map_id = state.ids.abilities[id_index]; - state.update(|s| { - s.id_ability_map.insert(map_id, *ability); - }); - - let ability_image = ability_id - .map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)); - let ability_color = if self.show.diary_fields.selected_ability != Some(*ability) - { - TEXT_COLOR - } else { - XP_COLOR - }; let (ability_title, ability_desc) = util::ability_description(ability_id.unwrap_or("")); + let (align_state, image_offsets) = if id_index < 6 { (state.ids.sb_page_left_align, 120.0 * id_index as f64) } else { (state.ids.sb_page_right_align, 120.0 * (id_index - 6) as f64) }; + Image::new(if same_weap_kinds { self.imgs.ability_frame_dual } else { @@ -1132,37 +1141,22 @@ impl<'a> Widget for Diary<'a> { .color(Some(UI_HIGHLIGHT_0)) //.parent(state.ids.abilities[id_index]) .set(state.ids.ability_frames[id_index], ui); - if Button::image(ability_image) - .w_h(100.0, 100.0) + + let slot = AbilitySlot::Ability(*ability); + slot_maker + .fabricate(slot, [100.0; 2]) .top_left_with_margins_on(align_state, 20.0 + image_offsets, 20.0) - .set(state.ids.abilities[id_index], ui) - .was_clicked() - { - if Some(*ability) != self.show.diary_fields.selected_ability { - events.push(Event::SelectAbility(Some(*ability))); - } else { - events.push(Event::SelectAbility(None)); - } - } + .set(state.ids.abilities[id_index], ui); + if same_weap_kinds { if let AuxiliaryAbility::MainWeapon(slot) = ability { let ability = AuxiliaryAbility::OffWeapon(*slot); - let map_id = state.ids.abilities_dual[id_index]; - state.update(|s| { - s.id_ability_map.insert(map_id, ability); - }); - if Button::image(ability_image) - .w_h(100.0, 100.0) + + let slot = AbilitySlot::Ability(ability); + slot_maker + .fabricate(slot, [100.0; 2]) .top_right_with_margins_on(align_state, 20.0 + image_offsets, 20.0) - .set(state.ids.abilities_dual[id_index], ui) - .was_clicked() - { - if Some(ability) != self.show.diary_fields.selected_ability { - events.push(Event::SelectAbility(Some(ability))); - } else { - events.push(Event::SelectAbility(None)); - } - } + .set(state.ids.abilities_dual[id_index], ui); } } // The page width... @@ -1180,7 +1174,7 @@ impl<'a> Widget for Diary<'a> { .top_left_with_margins_on(state.ids.abilities[id_index], 5.0, 110.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(tweak!(28))) - .color(ability_color) + .color(TEXT_COLOR) .w(text_width) .graphics_for(state.ids.abilities[id_index]) .set(state.ids.ability_titles[id_index], ui); @@ -1192,56 +1186,12 @@ impl<'a> Widget for Diary<'a> { ) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(tweak!(18))) - .color(ability_color) + .color(TEXT_COLOR) .w(text_width) .graphics_for(state.ids.abilities[id_index]) .set(state.ids.ability_descs[id_index], ui); } - let mouse_pos = ui.global_input().current.mouse.xy; - // Handle dragging - if let Some(ability) = state.dragged_ability { - let ability_id = Ability::from(ability).ability_id(Some(self.inventory)); - if let Some(ability_id) = ability_id { - Image::new(util::ability_image(self.imgs, ability_id)) - .w_h(80.0, 80.0) - .xy(mouse_pos) - .set(state.ids.dragged_ability, ui); - } - } - let input = &ui.global_input().current; - match input.mouse.buttons.left() { - // If mouse button was pushed down over some id - ButtonPosition::Down(_, Some(id)) => { - if state.dragged_ability.is_none() { - if let Some(ability) = state.id_ability_map.get(id).copied() { - state.update(|s| { - s.dragged_ability = Some(ability); - }); - } - } - }, - ButtonPosition::Up => { - if let (Some(ability), Some(id)) = - (state.dragged_ability, input.widget_under_mouse) - { - if let Some(index) = state - .ids - .active_abilities - .iter() - .enumerate() - .find_map(|(i, slot_id)| (id == *slot_id).then_some(i)) - { - events.push(Event::ChangeAbility(index, ability)); - } - } - state.update(|s| { - s.dragged_ability = None; - }); - }, - _ => {}, - } - events }, DiarySection::Stats => { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index fd1b3e4426..861786b23f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -74,7 +74,9 @@ use client::Client; use common::{ combat, comp::{ - self, fluid_dynamics, + self, + ability::AuxiliaryAbility, + fluid_dynamics, inventory::{slot::InvSlotId, trade_pricing::TradePricing}, item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality}, skillset::{skills::Skill, SkillGroupKind}, @@ -535,7 +537,7 @@ pub enum Event { RemoveBuff(BuffKind), UnlockSkill(Skill), RequestSiteInfo(SiteId), - ChangeAbility(usize, comp::ability::AuxiliaryAbility), + ChangeAbility(usize, AuxiliaryAbility), SettingsChange(SettingsChange), AcknowledgePersistenceLoadError, @@ -3132,6 +3134,7 @@ impl Hud { i18n, &self.rot_imgs, tooltip_manager, + &mut self.slot_manager, self.pulse, ) .set(self.ids.diary, ui_widgets) @@ -3149,12 +3152,6 @@ impl Hud { diary::Event::ChangeSection(section) => { self.show.diary_fields.section = section; }, - diary::Event::SelectAbility(ability) => { - self.show.diary_fields.selected_ability = ability; - }, - diary::Event::ChangeAbility(slot, new_ability) => { - events.push(Event::ChangeAbility(slot, new_ability)) - }, } } } @@ -3311,7 +3308,7 @@ impl Hud { // Maintain slot manager 'slot_events: for event in self.slot_manager.maintain(ui_widgets) { use comp::slot::Slot; - use slots::{InventorySlot, SlotKind::*}; + use slots::{AbilitySlot, InventorySlot, SlotKind::*}; let to_slot = |slot_kind| match slot_kind { Inventory(InventorySlot { slot, ours: true, .. @@ -3320,6 +3317,7 @@ impl Hud { Equip(e) => Some(Slot::Equip(e)), Hotbar(_) => None, Trade(_) => None, + Ability(_) => None, }; match event { slot::Event::Dragged(a, b) => { @@ -3369,6 +3367,33 @@ impl Hud { } } } + } else if let (Ability(a), Ability(b)) = (a, b) { + match (a, b) { + (AbilitySlot::Ability(ability), AbilitySlot::Slot(index)) => { + events.push(Event::ChangeAbility(index, ability)); + }, + (AbilitySlot::Slot(a), AbilitySlot::Slot(b)) => { + let me = client.entity(); + if let Some(active_abilities) = active_abilities.get(me) { + let ability_a = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(a) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + let ability_b = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(b) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + events.push(Event::ChangeAbility(a, ability_b)); + events.push(Event::ChangeAbility(b, ability_a)); + } + }, + (AbilitySlot::Slot(index), _) => { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); + }, + (_, _) => {}, + } } }, slot::Event::Dropped(from) => { @@ -3388,6 +3413,8 @@ impl Hud { })); } } + } else if let Ability(AbilitySlot::Slot(index)) = from { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); } }, slot::Event::SplitDropped(from) => { @@ -3397,6 +3424,8 @@ impl Hud { } else if let Hotbar(h) = from { self.hotbar.clear_slot(h); events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned()))); + } else if let Ability(AbilitySlot::Slot(index)) = from { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); } }, slot::Event::SplitDragged(a, b) => { @@ -3440,6 +3469,33 @@ impl Hud { } } } + } else if let (Ability(a), Ability(b)) = (a, b) { + match (a, b) { + (AbilitySlot::Ability(ability), AbilitySlot::Slot(index)) => { + events.push(Event::ChangeAbility(index, ability)); + }, + (AbilitySlot::Slot(a), AbilitySlot::Slot(b)) => { + let me = client.entity(); + if let Some(active_abilities) = active_abilities.get(me) { + let ability_a = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(a) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + let ability_b = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(b) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + events.push(Event::ChangeAbility(a, ability_b)); + events.push(Event::ChangeAbility(b, ability_a)); + } + }, + (AbilitySlot::Slot(index), _) => { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); + }, + (_, _) => {}, + } } }, slot::Event::Used(from) => { @@ -3475,6 +3531,8 @@ impl Hud { }, hotbar::SlotContents::Ability(_) => {}, }); + } else if let Ability(AbilitySlot::Slot(index)) = from { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); } }, slot::Event::Request { diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 877e1a87d5..2d8bf99b8a 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -6,8 +6,9 @@ use super::{ }; use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{ - ability::AbilityInput, slot::InvSlotId, Ability, ActiveAbilities, Body, Energy, Inventory, - SkillSet, + ability::{Ability, AbilityInput, AuxiliaryAbility}, + slot::InvSlotId, + ActiveAbilities, Body, Energy, Inventory, SkillSet, }; use conrod_core::{image, Color}; use specs::Entity as EcsEntity; @@ -20,6 +21,7 @@ pub enum SlotKind { Equip(EquipSlot), Hotbar(HotbarSlot), Trade(TradeSlot), + Ability(AbilitySlot), /* Spellbook(SpellbookSlot), TODO */ } @@ -192,6 +194,42 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { } } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AbilitySlot { + Slot(usize), + Ability(AuxiliaryAbility), +} + +type AbilitiesSource<'a> = (&'a ActiveAbilities, &'a Inventory, &'a SkillSet); + +impl<'a> SlotKey, img_ids::Imgs> for AbilitySlot { + type ImageKey = String; + + fn image_key( + &self, + (active_abilities, inventory, skillset): &AbilitiesSource<'a>, + ) -> Option<(Self::ImageKey, Option)> { + let ability_id = match self { + Self::Slot(index) => active_abilities + .get_ability( + AbilityInput::Auxiliary(*index), + Some(inventory), + Some(skillset), + ) + .ability_id(Some(inventory)), + Self::Ability(ability) => Ability::from(*ability).ability_id(Some(inventory)), + }; + + ability_id.map(|id| (String::from(id), None)) + } + + fn amount(&self, _source: &AbilitiesSource) -> Option { None } + + fn image_ids(ability_id: &Self::ImageKey, imgs: &img_ids::Imgs) -> Vec { + vec![util::ability_image(imgs, ability_id)] + } +} + impl From for SlotKind { fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } } @@ -207,4 +245,10 @@ impl From for SlotKind { fn from(trade: TradeSlot) -> Self { Self::Trade(trade) } } -impl SumSlot for SlotKind {} +impl From for SlotKind { + fn from(ability: AbilitySlot) -> Self { Self::Ability(ability) } +} + +impl SumSlot for SlotKind { + fn is_ability(&self) -> bool { matches!(self, Self::Ability(_)) } +} diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index d6ce8933b7..b26296239c 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -19,7 +19,9 @@ pub trait SlotKey: Copy { fn image_ids(key: &Self::ImageKey, source: &I) -> Vec; } -pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static {} +pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static { + fn is_ability(&self) -> bool; +} pub struct ContentSize { // Width divided by height @@ -217,6 +219,12 @@ where let content_img = *content_img; let drag_amount = *drag_amount; + let dragged_size = if slot.is_ability() { + [inline_tweak::tweak!(80.0); 2] + } else { + self.drag_img_size.map(|e| e as f64).into_array() + }; + // If we are dragging and we right click, drop half the stack // on the ground or into the slot under the cursor. This only // works with open slots or slots containing the same kind of @@ -260,9 +268,8 @@ where // Draw image of contents being dragged let [mouse_x, mouse_y] = input.mouse.xy; - let size = self.drag_img_size.map(|e| e as f64).into_array(); super::ghost_image::GhostImage::new(content_img) - .wh(size) + .wh(dragged_size) .xy([mouse_x, mouse_y]) .set(self.drag_id, ui);