rework slot trait

This commit is contained in:
Imbris
2020-04-06 11:25:45 -04:00
committed by Pfauenauge90
parent e2d60b858e
commit 66b4c0d529
4 changed files with 115 additions and 105 deletions

View File

@ -1,7 +1,7 @@
use super::{ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs, item_imgs::ItemImgs,
slot_kinds::{ArmorSlot, HudSlotManager, InventorySlot}, slots::{ArmorSlot, InventorySlot, SlotManager},
Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
XP_COLOR, XP_COLOR,
}; };
@ -91,7 +91,7 @@ pub struct Bag<'a> {
common: widget::CommonBuilder, common: widget::CommonBuilder,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
slot_manager: &'a mut HudSlotManager, slot_manager: &'a mut SlotManager,
_pulse: f32, _pulse: f32,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
@ -107,7 +107,7 @@ impl<'a> Bag<'a> {
fonts: &'a ConrodVoxygenFonts, fonts: &'a ConrodVoxygenFonts,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
slot_manager: &'a mut HudSlotManager, slot_manager: &'a mut SlotManager,
pulse: f32, pulse: f32,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
stats: &'a Stats, stats: &'a Stats,

View File

@ -8,7 +8,7 @@ mod map;
mod minimap; mod minimap;
mod settings_window; mod settings_window;
mod skillbar; mod skillbar;
mod slot_kinds; mod slots;
mod social; mod social;
mod spell; mod spell;
@ -234,8 +234,8 @@ pub enum Event {
CharacterSelection, CharacterSelection,
UseInventorySlot(usize), UseInventorySlot(usize),
SwapInventorySlots(usize, usize), SwapInventorySlots(usize, usize),
SwapInventoryArmor(usize, slot_kinds::ArmorSlot), SwapInventoryArmor(usize, slots::ArmorSlot),
SwapArmorSlots(slot_kinds::ArmorSlot, slot_kinds::ArmorSlot), SwapArmorSlots(slots::ArmorSlot, slots::ArmorSlot),
DropInventorySlot(usize), DropInventorySlot(usize),
Logout, Logout,
Quit, Quit,
@ -443,7 +443,7 @@ pub struct Hud {
pulse: f32, pulse: f32,
velocity: f32, velocity: f32,
voxygen_i18n: std::sync::Arc<VoxygenLocalization>, voxygen_i18n: std::sync::Arc<VoxygenLocalization>,
slot_manager: slot_kinds::HudSlotManager, slot_manager: slots::SlotManager,
} }
impl Hud { impl Hud {
@ -474,8 +474,7 @@ impl Hud {
let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui)
.expect("Impossible to load fonts!"); .expect("Impossible to load fonts!");
let slot_manager = let slot_manager = slots::SlotManager::new(ui.id_generator(), Vec2::broadcast(40.0));
slot_kinds::HudSlotManager::new(ui.id_generator(), Vec2::broadcast(40.0));
Self { Self {
ui, ui,
@ -1962,29 +1961,26 @@ impl Hud {
// Maintain slot manager // Maintain slot manager
for event in self.slot_manager.maintain(ui_widgets) { for event in self.slot_manager.maintain(ui_widgets) {
use slot_kinds::HudSlotKinds; use slots::SlotKind;
match event { match event {
slot::Event::Dragged( slot::Event::Dragged(SlotKind::Inventory(from), SlotKind::Inventory(to)) => {
HudSlotKinds::Inventory(from),
HudSlotKinds::Inventory(to),
) => {
// Swap between inventory slots // Swap between inventory slots
events.push(Event::SwapInventorySlots(from.0, to.0)); events.push(Event::SwapInventorySlots(from.0, to.0));
}, },
slot::Event::Dragged(HudSlotKinds::Armor(from), HudSlotKinds::Armor(to)) => { slot::Event::Dragged(SlotKind::Armor(from), SlotKind::Armor(to)) => {
// Swap between two armor slots // Swap between two armor slots
events.push(Event::SwapArmorSlots(from, to)); events.push(Event::SwapArmorSlots(from, to));
}, },
slot::Event::Dragged(HudSlotKinds::Inventory(inv), HudSlotKinds::Armor(arm)) slot::Event::Dragged(SlotKind::Inventory(inv), SlotKind::Armor(arm))
| slot::Event::Dragged(HudSlotKinds::Armor(arm), HudSlotKinds::Inventory(inv)) => { | slot::Event::Dragged(SlotKind::Armor(arm), SlotKind::Inventory(inv)) => {
// Swap between inventory and armor slot // Swap between inventory and armor slot
events.push(Event::SwapInventoryArmor(inv.0, arm)); events.push(Event::SwapInventoryArmor(inv.0, arm));
}, },
slot::Event::Dropped(HudSlotKinds::Inventory(from)) => { slot::Event::Dropped(SlotKind::Inventory(from)) => {
// Drop item from inventory // Drop item from inventory
events.push(Event::DropInventorySlot(from.0)); events.push(Event::DropInventorySlot(from.0));
}, },
slot::Event::Used(HudSlotKinds::Inventory(inv)) => { slot::Event::Used(SlotKind::Inventory(inv)) => {
// Item in inventory used (selected and then clicked again) // Item in inventory used (selected and then clicked again)
events.push(Event::UseInventorySlot(inv.0)); events.push(Event::UseInventorySlot(inv.0));
}, },

View File

@ -1,17 +1,17 @@
use super::item_imgs::{ItemImgs, ItemKey}; use super::item_imgs::{ItemImgs, ItemKey};
use crate::ui::slot::{ContentKey, SlotKinds, SlotManager}; use crate::ui::slot::{self, SlotKey, SumSlot};
use common::comp::{item::ItemKind, Inventory, Loadout}; use common::comp::{item::ItemKind, Inventory, Loadout};
use conrod_core::image; use conrod_core::image;
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum HudSlotKinds { pub enum SlotKind {
Inventory(InventorySlot), Inventory(InventorySlot),
Armor(ArmorSlot), Armor(ArmorSlot),
Hotbar(HotbarSlot), /*Hotbar(HotbarSlot),
//Spellbook(SpellbookSlot), TODO *Spellbook(SpellbookSlot), TODO */
} }
pub type HudSlotManager = SlotManager<HudSlotKinds>; pub type SlotManager = slot::SlotManager<SlotKind>;
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub struct InventorySlot(pub usize); pub struct InventorySlot(pub usize);
@ -34,7 +34,7 @@ pub enum ArmorSlot {
Tabard, Tabard,
} }
#[derive(Clone, Copy, PartialEq)] /*#[derive(Clone, Copy, PartialEq)]
pub enum HotbarSlot { pub enum HotbarSlot {
One, One,
Two, Two,
@ -46,18 +46,16 @@ pub enum HotbarSlot {
Eight, Eight,
Nine, Nine,
Ten, Ten,
} }*/
impl ContentKey for InventorySlot { impl SlotKey<Inventory, ItemImgs> for InventorySlot {
type ContentSource = Inventory;
type ImageKey = ItemKey; type ImageKey = ItemKey;
type ImageSource = ItemImgs;
fn image_key(&self, source: &Self::ContentSource) -> Option<Self::ImageKey> { fn image_key(&self, source: &Inventory) -> Option<Self::ImageKey> {
source.get(self.0).map(Into::into) source.get(self.0).map(Into::into)
} }
fn amount(&self, source: &Self::ContentSource) -> Option<u32> { fn amount(&self, source: &Inventory) -> Option<u32> {
source source
.get(self.0) .get(self.0)
.and_then(|item| match item.kind { .and_then(|item| match item.kind {
@ -69,17 +67,15 @@ impl ContentKey for InventorySlot {
.filter(|amount| *amount > 1) .filter(|amount| *amount > 1)
} }
fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id { fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id {
source.img_id_or_not_found_img(key.clone()) source.img_id_or_not_found_img(key.clone())
} }
} }
impl ContentKey for ArmorSlot { impl SlotKey<Loadout, ItemImgs> for ArmorSlot {
type ContentSource = Loadout;
type ImageKey = ItemKey; type ImageKey = ItemKey;
type ImageSource = ItemImgs;
fn image_key(&self, source: &Self::ContentSource) -> Option<Self::ImageKey> { fn image_key(&self, source: &Loadout) -> Option<Self::ImageKey> {
let item = match self { let item = match self {
ArmorSlot::Shoulders => source.shoulder.as_ref(), ArmorSlot::Shoulders => source.shoulder.as_ref(),
ArmorSlot::Chest => source.chest.as_ref(), ArmorSlot::Chest => source.chest.as_ref(),
@ -101,23 +97,47 @@ impl ContentKey for ArmorSlot {
item.map(Into::into) item.map(Into::into)
} }
fn amount(&self, _: &Self::ContentSource) -> Option<u32> { None } fn amount(&self, _: &Loadout) -> Option<u32> { None }
fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id { fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id {
source.img_id_or_not_found_img(key.clone()) source.img_id_or_not_found_img(key.clone())
} }
} }
impl From<InventorySlot> for HudSlotKinds { /*impl SlotKey<Hotbar, ItemImgs> for HotbarSlot {
type ImageKey = ItemKey;
fn image_key(&self, source: &Inventory) -> Option<Self::ImageKey> {
source.get(self.0).map(Into::into)
}
fn amount(&self, source: &Inventory) -> 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: &ItemImgs) -> image::Id {
source.img_id_or_not_found_img(key.clone())
}
}*/
impl From<InventorySlot> for SlotKind {
fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) }
} }
impl From<ArmorSlot> for HudSlotKinds { impl From<ArmorSlot> for SlotKind {
fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) } fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) }
} }
impl From<HotbarSlot> for HudSlotKinds { //impl From<HotbarSlot> for SlotKind {
fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) } // fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) }
} //}
impl SlotKinds for HudSlotKinds {} impl SumSlot for SlotKind {}

View File

@ -10,18 +10,15 @@ use vek::*;
const AMOUNT_SHADOW_OFFSET: [f64; 2] = [1.0, 1.0]; const AMOUNT_SHADOW_OFFSET: [f64; 2] = [1.0, 1.0];
pub trait ContentKey: Copy { pub trait SlotKey<C, I>: Copy {
type ContentSource;
type ImageSource;
type ImageKey: PartialEq + Send + 'static; type ImageKey: PartialEq + Send + 'static;
/// Returns an Option since the slot could be empty /// Returns an Option since the slot could be empty
fn image_key(&self, source: &Self::ContentSource) -> Option<Self::ImageKey>; fn image_key(&self, source: &C) -> Option<Self::ImageKey>;
// TODO: is this the right integer type? fn amount(&self, source: &C) -> Option<u32>;
fn amount(&self, source: &Self::ContentSource) -> Option<u32>; fn image_id(key: &Self::ImageKey, source: &I) -> image::Id;
fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id;
} }
pub trait SlotKinds: Sized + PartialEq + Copy + Send + 'static {} pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static {}
pub struct ContentSize { pub struct ContentSize {
// Width divided by height // Width divided by height
@ -30,7 +27,7 @@ pub struct ContentSize {
pub max_fraction: f32, pub max_fraction: f32,
} }
pub struct SlotMaker<'a, C: ContentKey + Into<K>, K: SlotKinds> { pub struct SlotMaker<'a, C, I, S: SumSlot> {
pub empty_slot: image::Id, pub empty_slot: image::Id,
pub filled_slot: image::Id, pub filled_slot: image::Id,
pub selected_slot: image::Id, pub selected_slot: image::Id,
@ -43,17 +40,20 @@ pub struct SlotMaker<'a, C: ContentKey + Into<K>, K: SlotKinds> {
pub amount_font_size: u32, pub amount_font_size: u32,
pub amount_margins: Vec2<f32>, pub amount_margins: Vec2<f32>,
pub amount_text_color: Color, pub amount_text_color: Color,
pub content_source: &'a C::ContentSource, pub content_source: &'a C,
pub image_source: &'a C::ImageSource, pub image_source: &'a I,
pub slot_manager: Option<&'a mut SlotManager<K>>, pub slot_manager: Option<&'a mut SlotManager<S>>,
} }
impl<'a, C, K> SlotMaker<'a, C, K> impl<'a, C, I, S> SlotMaker<'a, C, I, S>
where where
C: ContentKey + Into<K>, S: SumSlot,
K: SlotKinds,
{ {
pub fn fabricate(&mut self, contents: C, wh: [f32; 2]) -> Slot<C, K> { pub fn fabricate<K: SlotKey<C, I> + Into<S>>(
&mut self,
contents: K,
wh: [f32; 2],
) -> Slot<K, C, I, S> {
let content_size = { let content_size = {
let ContentSize { let ContentSize {
max_fraction, max_fraction,
@ -111,13 +111,13 @@ pub enum Event<K> {
Used(K), Used(K),
} }
// Handles interactions with slots // Handles interactions with slots
pub struct SlotManager<K: SlotKinds> { pub struct SlotManager<S: SumSlot> {
state: ManagerState<K>, state: ManagerState<S>,
// Rebuilt every frame // Rebuilt every frame
slot_ids: Vec<widget::Id>, slot_ids: Vec<widget::Id>,
// Rebuilt every frame // Rebuilt every frame
slot_kinds: Vec<K>, slots: Vec<S>,
events: Vec<Event<K>>, events: Vec<Event<S>>,
// Widget id for dragging image // Widget id for dragging image
drag_id: widget::Id, drag_id: widget::Id,
// Size to display dragged content // Size to display dragged content
@ -125,25 +125,25 @@ pub struct SlotManager<K: SlotKinds> {
drag_img_size: Vec2<f32>, drag_img_size: Vec2<f32>,
} }
impl<K> SlotManager<K> impl<S> SlotManager<S>
where where
K: SlotKinds, S: SumSlot,
{ {
pub fn new(mut gen: widget::id::Generator, drag_img_size: Vec2<f32>) -> Self { pub fn new(mut gen: widget::id::Generator, drag_img_size: Vec2<f32>) -> Self {
Self { Self {
state: ManagerState::Idle, state: ManagerState::Idle,
slot_ids: Vec::new(), slot_ids: Vec::new(),
slot_kinds: Vec::new(), slots: Vec::new(),
events: Vec::new(), events: Vec::new(),
drag_id: gen.next(), drag_id: gen.next(),
drag_img_size, drag_img_size,
} }
} }
pub fn maintain(&mut self, ui: &mut conrod_core::UiCell) -> Vec<Event<K>> { pub fn maintain(&mut self, ui: &mut conrod_core::UiCell) -> Vec<Event<S>> {
// Clear // Clear
let slot_ids = std::mem::replace(&mut self.slot_ids, Vec::new()); let slot_ids = std::mem::replace(&mut self.slot_ids, Vec::new());
let slot_kinds = std::mem::replace(&mut self.slot_kinds, Vec::new()); let slots = std::mem::replace(&mut self.slots, Vec::new());
// Detect drops by of selected item by clicking in empty space // Detect drops by of selected item by clicking in empty space
if let ManagerState::Selected(_, slot) = self.state { if let ManagerState::Selected(_, slot) = self.state {
@ -166,7 +166,7 @@ where
self.events.push(Event::Dropped(*slot)); self.events.push(Event::Dropped(*slot));
} else if let Some(idx) = slot_ids.iter().position(|slot_id| *slot_id == id) { } else if let Some(idx) = slot_ids.iter().position(|slot_id| *slot_id == id) {
// If widget is a slot widget swap with it // If widget is a slot widget swap with it
self.events.push(Event::Dragged(*slot, slot_kinds[idx])); self.events.push(Event::Dragged(*slot, slots[idx]));
} }
} }
// Mouse released stop dragging // Mouse released stop dragging
@ -187,13 +187,13 @@ where
fn update( fn update(
&mut self, &mut self,
widget: widget::Id, widget: widget::Id,
slot: K, slot: S,
ui: &conrod_core::Ui, ui: &conrod_core::Ui,
content_img: Option<image::Id>, content_img: Option<image::Id>,
) -> Interaction { ) -> Interaction {
// Add to list of slots // Add to list of slots
self.slot_ids.push(widget); self.slot_ids.push(widget);
self.slot_kinds.push(slot); self.slots.push(slot);
let filled = content_img.is_some(); let filled = content_img.is_some();
// If the slot is no longer filled deselect it or cancel dragging // If the slot is no longer filled deselect it or cancel dragging
@ -280,8 +280,8 @@ where
} }
#[derive(WidgetCommon)] #[derive(WidgetCommon)]
pub struct Slot<'a, C: ContentKey + Into<K>, K: SlotKinds> { pub struct Slot<'a, K: SlotKey<C, I> + Into<S>, C, I, S: SumSlot> {
content: C, slot_key: K,
// Images for slot background and frame // Images for slot background and frame
empty_slot: image::Id, empty_slot: image::Id,
@ -301,10 +301,10 @@ pub struct Slot<'a, C: ContentKey + Into<K>, K: SlotKinds> {
amount_margins: Vec2<f32>, amount_margins: Vec2<f32>,
amount_text_color: Color, amount_text_color: Color,
slot_manager: Option<&'a mut SlotManager<K>>, slot_manager: Option<&'a mut SlotManager<S>>,
// Should we just pass in the ImageKey? // Should we just pass in the ImageKey?
content_source: &'a C::ContentSource, content_source: &'a C,
image_source: &'a C::ImageSource, image_source: &'a I,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
@ -322,29 +322,32 @@ widget_ids! {
} }
/// Represents the state of the Slot widget. /// Represents the state of the Slot widget.
pub struct State<S: SlotKinds, K> { pub struct State<K> {
ids: Ids, ids: Ids,
cached_image: Option<(K, image::Id)>, cached_image: Option<(K, image::Id)>,
slot_kind: S,
} }
impl<'a, C, K> Slot<'a, C, K> impl<'a, K, C, I, S> Slot<'a, K, C, I, S>
where where
C: ContentKey + Into<K>, K: SlotKey<C, I> + Into<S>,
K: SlotKinds, S: SumSlot,
{ {
builder_methods! { builder_methods! {
pub with_manager { slot_manager = Some(&'a mut SlotManager<K>) }
pub with_background_color { background_color = Some(Color) } pub with_background_color { background_color = Some(Color) }
} }
pub fn with_manager(mut self, slot_manager: &'a mut SlotManager<S>) -> Self {
self.slot_manager = Some(slot_manager);
self
}
pub fn with_icon(mut self, img: image::Id, size: Vec2<f32>, color: Option<Color>) -> Self { pub fn with_icon(mut self, img: image::Id, size: Vec2<f32>, color: Option<Color>) -> Self {
self.icon = Some((img, size, color)); self.icon = Some((img, size, color));
self self
} }
fn new( fn new(
content: C, slot_key: K,
empty_slot: image::Id, empty_slot: image::Id,
filled_slot: image::Id, filled_slot: image::Id,
selected_slot: image::Id, selected_slot: image::Id,
@ -354,11 +357,11 @@ where
amount_font_size: u32, amount_font_size: u32,
amount_margins: Vec2<f32>, amount_margins: Vec2<f32>,
amount_text_color: Color, amount_text_color: Color,
content_source: &'a C::ContentSource, content_source: &'a C,
image_source: &'a C::ImageSource, image_source: &'a I,
) -> Self { ) -> Self {
Self { Self {
content, slot_key,
empty_slot, empty_slot,
filled_slot, filled_slot,
selected_slot, selected_slot,
@ -378,20 +381,19 @@ where
} }
} }
impl<'a, C, K> Widget for Slot<'a, C, K> impl<'a, K, C, I, S> Widget for Slot<'a, K, C, I, S>
where where
C: ContentKey + Into<K>, K: SlotKey<C, I> + Into<S>,
K: SlotKinds, S: SumSlot,
{ {
type Event = (); type Event = ();
type State = State<K, C::ImageKey>; type State = State<K::ImageKey>;
type Style = (); type Style = ();
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State { State {
ids: Ids::new(id_gen), ids: Ids::new(id_gen),
cached_image: None, cached_image: None,
slot_kind: self.content.into(),
} }
} }
@ -407,7 +409,7 @@ where
.. ..
} = args; } = args;
let Slot { let Slot {
content, slot_key,
empty_slot, empty_slot,
filled_slot, filled_slot,
selected_slot, selected_slot,
@ -425,29 +427,21 @@ where
} = self; } = self;
// If the key changed update the cached image id // If the key changed update the cached image id
let image_key = content.image_key(content_source); let image_key = slot_key.image_key(content_source);
if state.cached_image.as_ref().map(|c| &c.0) != image_key.as_ref() { if state.cached_image.as_ref().map(|c| &c.0) != image_key.as_ref() {
state.update(|state| { state.update(|state| {
state.cached_image = image_key.map(|key| { state.cached_image = image_key.map(|key| {
let image_id = C::image_id(&key, &image_source); let image_id = K::image_id(&key, &image_source);
(key, image_id) (key, image_id)
}); });
}); });
} }
// If the slot kind value changed update the state
let slot_kind = content.into();
if slot_kind != state.slot_kind {
state.update(|state| {
state.slot_kind = slot_kind;
});
}
// Get image ids // Get image ids
let content_image = state.cached_image.as_ref().map(|c| c.1); let content_image = state.cached_image.as_ref().map(|c| c.1);
// Get whether this slot is selected // Get whether this slot is selected
let interaction = self.slot_manager.map_or(Interaction::None, |m| { let interaction = self.slot_manager.map_or(Interaction::None, |m| {
m.update(id, content.into(), ui, content_image) m.update(id, slot_key.into(), ui, content_image)
}); });
// No content if it is being dragged // No content if it is being dragged
let content_image = if let Interaction::Dragging = interaction { let content_image = if let Interaction::Dragging = interaction {
@ -465,7 +459,7 @@ where
}; };
// Get amount (None => no amount text) // Get amount (None => no amount text)
let amount = content.amount(content_source); let amount = slot_key.amount(content_source);
// Get slot widget dimensions and position // Get slot widget dimensions and position
let (x, y, w, h) = rect.x_y_w_h(); let (x, y, w, h) = rect.x_y_w_h();