mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Setup basic generic slot system and used it for the inventory slots
This commit is contained in:
parent
ff4894e454
commit
9c76bdde0e
@ -1,21 +1,25 @@
|
||||
use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::{ItemImgs, ItemKey},
|
||||
slot_kinds::{HudSlotManager, InventorySlot},
|
||||
Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
XP_COLOR,
|
||||
};
|
||||
use crate::{
|
||||
i18n::VoxygenLocalization,
|
||||
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
|
||||
ui::{
|
||||
fonts::ConrodVoxygenFonts, slot::SlotMaker, ImageFrame, Tooltip, TooltipManager,
|
||||
Tooltipable,
|
||||
},
|
||||
};
|
||||
use client::Client;
|
||||
use common::comp::{item::ItemKind, Stats};
|
||||
use common::comp::Stats;
|
||||
use conrod_core::{
|
||||
color, image,
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
//
|
||||
use vek::Vec2;
|
||||
|
||||
widget_ids! {
|
||||
pub struct Ids {
|
||||
@ -93,7 +97,6 @@ widget_ids! {
|
||||
}
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Bag<'a> {
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
@ -103,7 +106,7 @@ pub struct Bag<'a> {
|
||||
common: widget::CommonBuilder,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
pulse: f32,
|
||||
_pulse: f32,
|
||||
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
|
||||
stats: &'a Stats,
|
||||
show: &'a Show,
|
||||
@ -130,7 +133,7 @@ impl<'a> Bag<'a> {
|
||||
common: widget::CommonBuilder::default(),
|
||||
rot_imgs,
|
||||
tooltip_manager,
|
||||
pulse,
|
||||
_pulse: pulse,
|
||||
localized_strings,
|
||||
stats,
|
||||
show,
|
||||
@ -603,57 +606,37 @@ impl<'a> Widget for Bag<'a> {
|
||||
.resize(inventory.len(), &mut ui.widget_id_generator());
|
||||
});
|
||||
}
|
||||
if state.ids.items.len() < inventory.len() {
|
||||
state.update(|s| {
|
||||
s.ids
|
||||
.items
|
||||
.resize(inventory.len(), &mut ui.widget_id_generator());
|
||||
});
|
||||
}
|
||||
if state.ids.amounts.len() < inventory.len() {
|
||||
state.update(|s| {
|
||||
s.ids
|
||||
.amounts
|
||||
.resize(inventory.len(), &mut ui.widget_id_generator());
|
||||
});
|
||||
}
|
||||
if state.ids.amounts_bg.len() < inventory.len() {
|
||||
state.update(|s| {
|
||||
s.ids
|
||||
.amounts_bg
|
||||
.resize(inventory.len(), &mut ui.widget_id_generator());
|
||||
});
|
||||
}
|
||||
// Expand img id cache to the number of slots
|
||||
if state.img_id_cache.len() < inventory.len() {
|
||||
state.update(|s| {
|
||||
s.img_id_cache.resize(inventory.len(), None);
|
||||
});
|
||||
}
|
||||
|
||||
// Display inventory contents
|
||||
// TODO: add slot manager
|
||||
let slot_manager: Option<&mut HudSlotManager> = None;
|
||||
let mut slot_maker = SlotMaker {
|
||||
background: self.imgs.inv_slot,
|
||||
selected_background: self.imgs.inv_slot_sel,
|
||||
background_color: Some(UI_MAIN),
|
||||
content_size: Vec2::broadcast(30.0),
|
||||
selected_content_size: Vec2::broadcast(32.0),
|
||||
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: inventory,
|
||||
image_source: self.item_imgs,
|
||||
slot_manager,
|
||||
};
|
||||
for (i, item) in inventory.slots().iter().enumerate() {
|
||||
let x = i % 9;
|
||||
let y = i / 9;
|
||||
|
||||
let is_selected = Some(i) == state.selected_slot;
|
||||
|
||||
// Slot
|
||||
|
||||
let slot_widget = Button::image(if !is_selected {
|
||||
self.imgs.inv_slot
|
||||
} else {
|
||||
self.imgs.inv_slot_sel
|
||||
})
|
||||
.top_left_with_margins_on(
|
||||
state.ids.inv_alignment,
|
||||
0.0 + y as f64 * (40.0),
|
||||
0.0 + x as f64 * (40.0),
|
||||
)
|
||||
.wh([40.0; 2])
|
||||
.image_color(UI_MAIN);
|
||||
|
||||
let slot_widget_clicked = if let Some(item) = item {
|
||||
let slot_widget = slot_maker
|
||||
.fabricate(InventorySlot(i))
|
||||
.top_left_with_margins_on(
|
||||
state.ids.inv_alignment,
|
||||
0.0 + y as f64 * (40.0),
|
||||
0.0 + x as f64 * (40.0),
|
||||
)
|
||||
.wh([40.0; 2]);
|
||||
if let Some(item) = item {
|
||||
slot_widget
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
@ -664,82 +647,15 @@ impl<'a> Widget for Bag<'a> {
|
||||
),
|
||||
&item_tooltip,
|
||||
)
|
||||
.set(state.ids.inv_slots[i], ui)
|
||||
.set(state.ids.inv_slots[i], ui);
|
||||
} else {
|
||||
slot_widget.set(state.ids.inv_slots[i], ui)
|
||||
}
|
||||
.was_clicked();
|
||||
|
||||
// Item
|
||||
if slot_widget_clicked {
|
||||
let selected_slot = match state.selected_slot {
|
||||
Some(a) => {
|
||||
if a == i {
|
||||
event = Some(Event::HudEvent(HudEvent::UseInventorySlot(i)));
|
||||
} else {
|
||||
event = Some(Event::HudEvent(HudEvent::SwapInventorySlots(a, i)));
|
||||
}
|
||||
None
|
||||
},
|
||||
None if item.is_some() => Some(i),
|
||||
None => None,
|
||||
};
|
||||
state.update(|s| s.selected_slot = selected_slot);
|
||||
}
|
||||
// Item
|
||||
if let Some(kind) = item.as_ref().map(|i| ItemKey::from(i)) {
|
||||
//Stack Size
|
||||
Button::image(match &state.img_id_cache[i] {
|
||||
Some((cached_kind, id)) if cached_kind == &kind => *id,
|
||||
_ => {
|
||||
let id = self
|
||||
.item_imgs
|
||||
.img_id(kind.clone())
|
||||
.unwrap_or(self.imgs.not_found);
|
||||
state.update(|s| s.img_id_cache[i] = Some((kind, id)));
|
||||
id
|
||||
},
|
||||
})
|
||||
.wh(if is_selected { [32.0; 2] } else { [30.0; 2] })
|
||||
.middle_of(state.ids.inv_slots[i])
|
||||
.graphics_for(state.ids.inv_slots[i])
|
||||
.set(state.ids.items[i], ui);
|
||||
}
|
||||
if let Some(item) = item {
|
||||
if let Some(amount) = match item.kind {
|
||||
ItemKind::Tool { .. } | ItemKind::Armor { .. } => None,
|
||||
ItemKind::Utility { amount, .. }
|
||||
| ItemKind::Consumable { amount, .. }
|
||||
| ItemKind::Ingredient { amount, .. } => Some(amount),
|
||||
} {
|
||||
if amount > 1 {
|
||||
Text::new(&format!("{}", &amount))
|
||||
.top_right_with_margins_on(state.ids.items[i], -4.0, 0.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.floating(true)
|
||||
.font_size(self.fonts.cyri.scale(12))
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.floating(true)
|
||||
.set(state.ids.amounts_bg[i], ui);
|
||||
Text::new(&format!("{}", &amount))
|
||||
.bottom_left_with_margins_on(state.ids.amounts_bg[i], 1.0, 1.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(12))
|
||||
.color(TEXT_COLOR)
|
||||
.floating(true)
|
||||
.set(state.ids.amounts[i], ui);
|
||||
}
|
||||
}
|
||||
slot_widget.set(state.ids.inv_slots[i], ui);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop selected item
|
||||
if let Some(to_drop) = state.selected_slot {
|
||||
if ui.widget_input(ui.window).clicks().left().next().is_some() {
|
||||
event = Some(Event::HudEvent(HudEvent::DropInventorySlot(to_drop)));
|
||||
state.update(|s| s.selected_slot = None);
|
||||
}
|
||||
}
|
||||
// if ui.widget_input(ui.window).clicks().left().next().is_some() {
|
||||
|
||||
// Stats Button
|
||||
if Button::image(self.imgs.button)
|
||||
.w_h(92.0, 22.0)
|
||||
|
@ -82,12 +82,14 @@ impl Asset for ItemImagesSpec {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: when there are more images don't load them all into memory
|
||||
pub struct ItemImgs {
|
||||
map: HashMap<ItemKey, Id>,
|
||||
indicator: ReloadIndicator,
|
||||
not_found: Id,
|
||||
}
|
||||
impl ItemImgs {
|
||||
pub fn new(ui: &mut Ui) -> Self {
|
||||
pub fn new(ui: &mut Ui, not_found: Id) -> Self {
|
||||
let mut indicator = ReloadIndicator::new();
|
||||
Self {
|
||||
map: assets::load_watched::<ItemImagesSpec>(
|
||||
@ -97,9 +99,13 @@ impl ItemImgs {
|
||||
.expect("Unable to load item image manifest")
|
||||
.0
|
||||
.iter()
|
||||
// TODO: what if multiple kinds map to the same image, it would be nice to use the same
|
||||
// image id for both, although this does interfere with the current hot-reloading
|
||||
// strategy
|
||||
.map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic())))
|
||||
.collect(),
|
||||
indicator,
|
||||
not_found,
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,6 +145,10 @@ impl ItemImgs {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn img_id_or_not_found_img(&self, item_kind: ItemKey) -> Id {
|
||||
self.img_id(item_kind).unwrap_or(self.not_found)
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from figure/load.rs
|
||||
|
@ -8,6 +8,7 @@ mod map;
|
||||
mod minimap;
|
||||
mod settings_window;
|
||||
mod skillbar;
|
||||
mod slot_kinds;
|
||||
mod social;
|
||||
mod spell;
|
||||
|
||||
@ -461,7 +462,7 @@ impl Hud {
|
||||
// Load rotation images.
|
||||
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
|
||||
// Load item images.
|
||||
let item_imgs = ItemImgs::new(&mut ui);
|
||||
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
|
||||
// Load language
|
||||
let voxygen_i18n = load_expect::<VoxygenLocalization>(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
|
91
voxygen/src/hud/slot_kinds.rs
Normal file
91
voxygen/src/hud/slot_kinds.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use super::item_imgs::{ItemImgs, ItemKey};
|
||||
use crate::ui::slot::{ContentKey, SlotKinds, SlotManager};
|
||||
use common::comp::{item::ItemKind, Inventory};
|
||||
use conrod_core::image;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum HudSlotKinds {
|
||||
Inventory(InventorySlot),
|
||||
Armor(ArmorSlot),
|
||||
Hotbar(HotbarSlot),
|
||||
}
|
||||
|
||||
pub type HudSlotManager = SlotManager<HudSlotKinds>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct InventorySlot(pub usize);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum ArmorSlot {
|
||||
Helmet,
|
||||
Neck,
|
||||
Shoulders,
|
||||
Chest,
|
||||
Hands,
|
||||
LeftRing,
|
||||
RightRing,
|
||||
Back,
|
||||
Belt,
|
||||
Legs,
|
||||
Feet,
|
||||
Mainhand,
|
||||
Offhand,
|
||||
Tabard,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum HotbarSlot {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Nine,
|
||||
Ten,
|
||||
}
|
||||
|
||||
impl ContentKey for InventorySlot {
|
||||
type ContentSource = Inventory;
|
||||
type ImageKey = ItemKey;
|
||||
type ImageSource = ItemImgs;
|
||||
|
||||
fn image_key(&self, source: &Self::ContentSource) -> Option<Self::ImageKey> {
|
||||
source.get(self.0).map(Into::into)
|
||||
}
|
||||
|
||||
fn amount(&self, source: &Self::ContentSource) -> Option<u32> {
|
||||
source
|
||||
.get(self.0)
|
||||
.and_then(|item| match item.kind {
|
||||
ItemKind::Tool { .. } | ItemKind::Armor { .. } => None,
|
||||
ItemKind::Utility { amount, .. }
|
||||
| ItemKind::Consumable { amount, .. }
|
||||
| ItemKind::Ingredient { amount, .. } => Some(amount),
|
||||
})
|
||||
.filter(|amount| *amount > 1)
|
||||
}
|
||||
|
||||
fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id {
|
||||
source.img_id_or_not_found_img(key.clone())
|
||||
}
|
||||
|
||||
fn back_icon(&self, _: &Self::ImageSource) -> Option<(image::Id, Vec2<f32>)> { None }
|
||||
}
|
||||
|
||||
impl From<InventorySlot> for HudSlotKinds {
|
||||
fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) }
|
||||
}
|
||||
|
||||
impl From<ArmorSlot> for HudSlotKinds {
|
||||
fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) }
|
||||
}
|
||||
|
||||
impl From<HotbarSlot> for HudSlotKinds {
|
||||
fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) }
|
||||
}
|
||||
|
||||
impl SlotKinds for HudSlotKinds {}
|
@ -16,6 +16,7 @@ pub use widgets::{
|
||||
image_slider::ImageSlider,
|
||||
ingame::{Ingame, Ingameable},
|
||||
radio_list::RadioList,
|
||||
slot,
|
||||
toggle_button::ToggleButton,
|
||||
tooltip::{Tooltip, TooltipManager, Tooltipable},
|
||||
};
|
||||
@ -28,6 +29,7 @@ use crate::{
|
||||
window::Window,
|
||||
Error,
|
||||
};
|
||||
#[rustfmt::skip]
|
||||
use ::image::GenericImageView;
|
||||
use cache::Cache;
|
||||
use common::{assets, util::srgba_to_linear};
|
||||
|
@ -2,5 +2,6 @@ pub mod image_frame;
|
||||
pub mod image_slider;
|
||||
pub mod ingame;
|
||||
pub mod radio_list;
|
||||
pub mod slot;
|
||||
pub mod toggle_button;
|
||||
pub mod tooltip;
|
||||
|
417
voxygen/src/ui/widgets/slot.rs
Normal file
417
voxygen/src/ui/widgets/slot.rs
Normal file
@ -0,0 +1,417 @@
|
||||
//! A widget for selecting a single value along some linear range.
|
||||
use conrod_core::{
|
||||
builder_methods, image,
|
||||
text::font,
|
||||
widget::{self, Image, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
const AMOUNT_SHADOW_OFFSET: [f64; 2] = [1.0, 1.0];
|
||||
|
||||
pub trait ContentKey: Copy {
|
||||
type ContentSource;
|
||||
type ImageSource;
|
||||
type ImageKey: PartialEq + Send + 'static;
|
||||
/// Returns an Option since the slot could be empty
|
||||
fn image_key(&self, source: &Self::ContentSource) -> Option<Self::ImageKey>;
|
||||
// TODO: is this the right integer type?
|
||||
fn amount(&self, source: &Self::ContentSource) -> Option<u32>;
|
||||
fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id;
|
||||
/// Returns slot icon and icon size as fraction of slot size
|
||||
fn back_icon(&self, source: &Self::ImageSource) -> Option<(image::Id, Vec2<f32>)>;
|
||||
}
|
||||
|
||||
pub trait SlotKinds: Sized + PartialEq + Copy {}
|
||||
|
||||
pub struct SlotMaker<'a, C: ContentKey + Into<K>, K: SlotKinds> {
|
||||
pub background: image::Id,
|
||||
pub selected_background: image::Id,
|
||||
pub background_color: Option<Color>,
|
||||
pub content_size: Vec2<f32>,
|
||||
pub selected_content_size: Vec2<f32>,
|
||||
pub amount_font: font::Id,
|
||||
pub amount_font_size: u32,
|
||||
pub amount_margins: Vec2<f32>,
|
||||
pub amount_text_color: Color,
|
||||
pub content_source: &'a C::ContentSource,
|
||||
pub image_source: &'a C::ImageSource,
|
||||
pub slot_manager: Option<&'a mut SlotManager<K>>,
|
||||
}
|
||||
|
||||
impl<'a, C, K> SlotMaker<'a, C, K>
|
||||
where
|
||||
C: ContentKey + Into<K>,
|
||||
K: SlotKinds,
|
||||
{
|
||||
pub fn fabricate(&mut self, contents: C) -> Slot<C, K> {
|
||||
Slot::new(
|
||||
contents,
|
||||
self.background,
|
||||
self.selected_background,
|
||||
self.content_size,
|
||||
self.selected_content_size,
|
||||
self.amount_font,
|
||||
self.amount_font_size,
|
||||
self.amount_margins,
|
||||
self.amount_text_color,
|
||||
self.content_source,
|
||||
self.image_source,
|
||||
)
|
||||
.and_then(self.background_color, |s, c| s.with_background_color(c))
|
||||
.and_then(self.slot_manager.as_mut(), |s, m| s.with_manager(m))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Note: will probably delete this
|
||||
// Integrate tooltip adding??
|
||||
use super::tooltip::{Tooltip, TooltipManager, Tooltipable};
|
||||
pub fn with_tooltips(
|
||||
self,
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
tooltip: &'a Tooltip<'a>,
|
||||
) -> TooltippedSlotMaker<'a, C, K> {
|
||||
TooltippedSlotMaker {
|
||||
slot_maker: self,
|
||||
tooltip_manager,
|
||||
tooltip,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tooltip: &'a Tooltip<'a>,
|
||||
slot_widget
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&item.name(),
|
||||
&format!(
|
||||
"{}",
|
||||
/* item.kind, item.effect(), */ item.description()
|
||||
),
|
||||
&item_tooltip,
|
||||
)
|
||||
|
||||
pub struct TooltippedSlotMaker<'a, C: ContentKey + Into<K>, K: SlotKinds> {
|
||||
pub slot_maker: SlotMaker<'a, C, K>,
|
||||
pub tooltip_manager: &'a mut TooltipManager,
|
||||
pub tooltip: &'a Tooltip,
|
||||
}*/
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ManagerState<K> {
|
||||
Dragging(widget::Id, K),
|
||||
Selected(widget::Id, K),
|
||||
Idle,
|
||||
}
|
||||
|
||||
enum Interaction {
|
||||
Selected,
|
||||
Dragging,
|
||||
None,
|
||||
}
|
||||
|
||||
enum Event<K> {
|
||||
// Dragged to another slot
|
||||
Dragged(K, K),
|
||||
// Dragged to open space
|
||||
Dropped(K),
|
||||
// Clicked while selected
|
||||
Used(K),
|
||||
}
|
||||
// Handles interactions with slots
|
||||
pub struct SlotManager<K: SlotKinds> {
|
||||
state: ManagerState<K>,
|
||||
events: Vec<Event<K>>,
|
||||
// widget id for dragging image
|
||||
}
|
||||
|
||||
impl<K> SlotManager<K>
|
||||
where
|
||||
K: SlotKinds,
|
||||
{
|
||||
fn update(&mut self, widget: widget::Id, slot: K, ui: &conrod_core::Ui) -> Interaction {
|
||||
// If this is the selected/dragged widget make sure the slot value is up to date
|
||||
match &mut self.state {
|
||||
ManagerState::Selected(id, stored_slot) | ManagerState::Dragging(id, stored_slot)
|
||||
if *id == widget =>
|
||||
{
|
||||
*stored_slot = slot
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// TODO: make more robust wrt multiple events in the same frame (eg event order
|
||||
// may matter) TODO: handle taps as well
|
||||
// TODO: handle clicks in empty space
|
||||
// TODO: handle drags
|
||||
let click_count = ui.widget_input(widget).clicks().left().count();
|
||||
if click_count > 0 {
|
||||
let odd_num_clicks = click_count % 2 == 1;
|
||||
self.state = if let ManagerState::Selected(id, other_slot) = self.state {
|
||||
if id != widget {
|
||||
// Swap
|
||||
if slot != other_slot {
|
||||
self.events.push(Event::Dragged(other_slot, slot));
|
||||
}
|
||||
if click_count == 1 {
|
||||
ManagerState::Idle
|
||||
} else if click_count == 2 {
|
||||
// Was clicked again
|
||||
ManagerState::Selected(widget, slot)
|
||||
} else {
|
||||
// Clicked more than once after swap, use and deselect
|
||||
self.events.push(Event::Used(slot));
|
||||
ManagerState::Idle
|
||||
}
|
||||
} else {
|
||||
// Clicked widget was already selected
|
||||
// Deselect and emit use if clicked while selected
|
||||
self.events.push(Event::Used(slot));
|
||||
ManagerState::Idle
|
||||
}
|
||||
} else {
|
||||
// No widgets were selected
|
||||
if odd_num_clicks {
|
||||
ManagerState::Selected(widget, slot)
|
||||
} else {
|
||||
// Selected and then deselected with one or more clicks
|
||||
ManagerState::Idle
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Determine whether this slot is being interacted with
|
||||
match self.state {
|
||||
ManagerState::Selected(id, _) if id == widget => Interaction::Selected,
|
||||
ManagerState::Dragging(id, _) if id == widget => Interaction::Dragging,
|
||||
_ => Interaction::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Slot<'a, C: ContentKey + Into<K>, K: SlotKinds> {
|
||||
content: C,
|
||||
|
||||
// Background is also the frame
|
||||
background: image::Id,
|
||||
selected_background: image::Id,
|
||||
background_color: Option<Color>,
|
||||
|
||||
// Size of content image
|
||||
content_size: Vec2<f32>,
|
||||
// TODO: maybe use constant scale factor or move this setting to the slot manager?
|
||||
selected_content_size: Vec2<f32>,
|
||||
|
||||
// Amount styling
|
||||
amount_font: font::Id,
|
||||
amount_font_size: u32,
|
||||
amount_margins: Vec2<f32>,
|
||||
amount_text_color: Color,
|
||||
|
||||
slot_manager: Option<&'a mut SlotManager<K>>,
|
||||
// Should we just pass in the ImageKey?
|
||||
content_source: &'a C::ContentSource,
|
||||
image_source: &'a C::ImageSource,
|
||||
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
}
|
||||
|
||||
widget_ids! {
|
||||
// Note: icon, amount, and amount_bg are not always used. Is there any cost to having them?
|
||||
struct Ids {
|
||||
background,
|
||||
icon,
|
||||
amount,
|
||||
amount_bg,
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the state of the Slot widget.
|
||||
pub struct State<K> {
|
||||
ids: Ids,
|
||||
cached_image: Option<(K, image::Id)>,
|
||||
}
|
||||
|
||||
impl<'a, C, K> Slot<'a, C, K>
|
||||
where
|
||||
C: ContentKey + Into<K>,
|
||||
K: SlotKinds,
|
||||
{
|
||||
builder_methods! {
|
||||
pub with_manager { slot_manager = Some(&'a mut SlotManager<K>) }
|
||||
pub with_background_color { background_color = Some(Color) }
|
||||
}
|
||||
|
||||
fn new(
|
||||
content: C,
|
||||
background: image::Id,
|
||||
selected_background: image::Id,
|
||||
content_size: Vec2<f32>,
|
||||
selected_content_size: Vec2<f32>,
|
||||
amount_font: font::Id,
|
||||
amount_font_size: u32,
|
||||
amount_margins: Vec2<f32>,
|
||||
amount_text_color: Color,
|
||||
content_source: &'a C::ContentSource,
|
||||
image_source: &'a C::ImageSource,
|
||||
) -> Self {
|
||||
Self {
|
||||
content,
|
||||
background,
|
||||
selected_background,
|
||||
background_color: None,
|
||||
content_size,
|
||||
selected_content_size,
|
||||
amount_font,
|
||||
amount_font_size,
|
||||
amount_margins,
|
||||
amount_text_color,
|
||||
slot_manager: None,
|
||||
content_source,
|
||||
image_source,
|
||||
common: widget::CommonBuilder::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C, K> Widget for Slot<'a, C, K>
|
||||
where
|
||||
C: ContentKey + Into<K>,
|
||||
K: SlotKinds,
|
||||
{
|
||||
type Event = ();
|
||||
type State = State<C::ImageKey>;
|
||||
type Style = ();
|
||||
|
||||
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||
State {
|
||||
ids: Ids::new(id_gen),
|
||||
cached_image: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn style(&self) -> Self::Style { () }
|
||||
|
||||
/// Update the state of the Slider.
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs {
|
||||
id,
|
||||
state,
|
||||
rect,
|
||||
ui,
|
||||
..
|
||||
} = args;
|
||||
let Slot {
|
||||
content,
|
||||
background,
|
||||
selected_background,
|
||||
background_color,
|
||||
content_size,
|
||||
selected_content_size,
|
||||
amount_font,
|
||||
amount_font_size,
|
||||
amount_margins,
|
||||
amount_text_color,
|
||||
content_source,
|
||||
image_source,
|
||||
..
|
||||
} = self;
|
||||
|
||||
// If the key changed update the cached image id
|
||||
let image_key = content.image_key(content_source);
|
||||
if state.cached_image.as_ref().map(|c| &c.0) != image_key.as_ref() {
|
||||
state.update(|state| {
|
||||
state.cached_image = image_key.map(|key| {
|
||||
let image_id = C::image_id(&key, &image_source);
|
||||
(key, image_id)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get whether this slot is selected
|
||||
let interaction = self
|
||||
.slot_manager
|
||||
.map_or(Interaction::None, |m| m.update(id, content.into(), ui));
|
||||
|
||||
// Get image ids
|
||||
let background_image = if let Interaction::Selected = interaction {
|
||||
selected_background
|
||||
} else {
|
||||
background
|
||||
};
|
||||
let icon = content.back_icon(image_source);
|
||||
let content_image = state.cached_image.as_ref().map(|c| c.1);
|
||||
|
||||
// Get amount (None => no amount text)
|
||||
let amount = content.amount(content_source);
|
||||
|
||||
// Get slot widget dimensions and position
|
||||
let (x, y, w, h) = rect.x_y_w_h();
|
||||
|
||||
// Draw background
|
||||
Image::new(background_image)
|
||||
.x_y(x, y)
|
||||
.w_h(w, h)
|
||||
.parent(id)
|
||||
.graphics_for(id)
|
||||
.color(background_color)
|
||||
.set(state.ids.background, ui);
|
||||
|
||||
// Draw icon
|
||||
if let Some((icon_image, size_frac)) = icon {
|
||||
let wh = (size_frac.map(|e| e as f64) * Vec2::new(w, h)).into_array();
|
||||
Image::new(icon_image)
|
||||
.x_y(x, y)
|
||||
.wh(wh)
|
||||
.parent(id)
|
||||
.graphics_for(id)
|
||||
.set(state.ids.icon, ui);
|
||||
}
|
||||
|
||||
// Draw contents
|
||||
if let Some(content_image) = content_image {
|
||||
Image::new(content_image)
|
||||
.x_y(x, y)
|
||||
.wh(if let Interaction::Selected = interaction {
|
||||
content_size
|
||||
} else {
|
||||
selected_content_size
|
||||
}
|
||||
.map(|e| e as f64)
|
||||
.into_array())
|
||||
.parent(id)
|
||||
.graphics_for(id)
|
||||
.set(state.ids.icon, ui);
|
||||
}
|
||||
|
||||
// Draw amount
|
||||
if let Some(amount) = amount {
|
||||
let amount = format!("{}", &amount);
|
||||
// Text shadow
|
||||
Text::new(&amount)
|
||||
.parent(id)
|
||||
.graphics_for(id)
|
||||
.font_id(amount_font)
|
||||
.font_size(amount_font_size)
|
||||
.top_right_with_margins_on(id, amount_margins.x as f64, amount_margins.y as f64)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.amount_bg, ui);
|
||||
Text::new(&amount)
|
||||
.parent(id)
|
||||
.graphics_for(id)
|
||||
.bottom_left_with_margins_on(
|
||||
state.ids.amount_bg,
|
||||
AMOUNT_SHADOW_OFFSET[0],
|
||||
AMOUNT_SHADOW_OFFSET[1],
|
||||
)
|
||||
.font_id(amount_font)
|
||||
.font_size(amount_font_size)
|
||||
.color(amount_text_color)
|
||||
.set(state.ids.amount, ui);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user