use std::cmp::max;
use std::collections::HashMap;
use rs_math3d::Dimensioni;
use crate::atlas::{AtlasHandle, EXPAND_DOWN_ICON};
use crate::input::{ControlState, ResourceState, WidgetBehaviourOption, WidgetOption};
use crate::style::Style;
use crate::widget_ctx::WidgetCtx;
use crate::widget_tree::WidgetHandle;
pub trait Widget {
fn widget_opt(&self) -> &WidgetOption;
fn behaviour_opt(&self) -> &WidgetBehaviourOption;
fn measure(&self, style: &Style, atlas: &AtlasHandle, avail: Dimensioni) -> Dimensioni;
fn run(&mut self, ctx: &mut WidgetCtx<'_>, control: &ControlState) -> ResourceState;
fn effective_widget_opt(&self) -> WidgetOption {
*self.widget_opt()
}
fn effective_behaviour_opt(&self) -> WidgetBehaviourOption {
*self.behaviour_opt()
}
fn needs_input_snapshot(&self) -> bool {
false
}
}
pub type WidgetId = *const ();
pub fn widget_id_of<W: Widget + ?Sized>(widget: &W) -> WidgetId {
widget as *const W as *const ()
}
pub fn widget_id_of_handle<W: Widget>(handle: &WidgetHandle<W>) -> WidgetId {
let widget = handle.borrow();
widget_id_of(&*widget)
}
#[derive(Default)]
pub(crate) struct FrameResults {
committed: HashMap<WidgetId, ResourceState>,
current: HashMap<WidgetId, ResourceState>,
current_dispatch_sites: HashMap<WidgetId, String>,
}
#[derive(Copy, Clone)]
pub struct FrameResultGeneration<'a> {
entries: &'a HashMap<WidgetId, ResourceState>,
}
impl<'a> FrameResultGeneration<'a> {
fn new(entries: &'a HashMap<WidgetId, ResourceState>) -> Self {
Self { entries }
}
pub fn state(&self, widget_id: WidgetId) -> ResourceState {
self.entries.get(&widget_id).copied().unwrap_or(ResourceState::NONE)
}
pub fn state_of<W: Widget + ?Sized>(&self, widget: &W) -> ResourceState {
self.state(widget_id_of(widget))
}
pub fn state_of_handle<W: Widget>(&self, handle: &WidgetHandle<W>) -> ResourceState {
self.state(widget_id_of_handle(handle))
}
}
impl FrameResults {
pub(crate) fn begin_frame(&mut self) {
self.current.clear();
self.current_dispatch_sites.clear();
}
pub(crate) fn finish_frame(&mut self) {
std::mem::swap(&mut self.committed, &mut self.current);
self.current.clear();
self.current_dispatch_sites.clear();
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn record(&mut self, widget_id: WidgetId, state: ResourceState) {
self.record_with_context(widget_id, state, "unknown widget dispatch site");
}
pub(crate) fn record_with_context(&mut self, widget_id: WidgetId, state: ResourceState, dispatch_site: impl Into<String>) {
let dispatch_site = dispatch_site.into();
if let Some(first_site) = self.current_dispatch_sites.get(&widget_id) {
panic!(
"duplicate widget dispatch detected for widget {:p}; a WidgetHandle may only be rendered once per frame. first dispatch: {}. duplicate dispatch: {}.",
widget_id, first_site, dispatch_site
);
}
let prev_state = self.current.insert(widget_id, state);
let prev_site = self.current_dispatch_sites.insert(widget_id, dispatch_site);
debug_assert_eq!(
prev_state.is_some(),
prev_site.is_some(),
"widget result and dispatch-site tracking diverged for widget {:p}",
widget_id
);
}
pub(crate) fn committed(&self) -> FrameResultGeneration<'_> {
FrameResultGeneration::new(&self.committed)
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn current(&self) -> FrameResultGeneration<'_> {
FrameResultGeneration::new(&self.current)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn committed_and_current_generation_views_are_explicit() {
let committed_widget = 1_u8;
let current_widget = 2_u8;
let committed_id = (&committed_widget as *const u8).cast::<()>();
let current_id = (¤t_widget as *const u8).cast::<()>();
let mut results = FrameResults::default();
results.record(committed_id, ResourceState::SUBMIT);
results.finish_frame();
results.begin_frame();
results.record(current_id, ResourceState::CHANGE);
assert!(results.committed().state(committed_id).is_submitted());
assert!(results.current().state(committed_id).is_none());
assert!(results.current().state(current_id).is_changed());
}
}
impl Widget for (WidgetOption, WidgetBehaviourOption) {
fn widget_opt(&self) -> &WidgetOption {
&self.0
}
fn behaviour_opt(&self) -> &WidgetBehaviourOption {
&self.1
}
fn measure(&self, style: &Style, atlas: &AtlasHandle, _avail: Dimensioni) -> Dimensioni {
let padding = style.padding.max(0);
let vertical_pad = max(1, padding / 2);
let font_height = atlas.get_font_height(style.font) as i32;
let icon_height = atlas.get_icon_size(EXPAND_DOWN_ICON).height;
let content = max(font_height, icon_height);
let height = (content + vertical_pad * 2).max(0);
let width = (padding * 2 + content).max(0);
Dimensioni::new(width, height)
}
fn run(&mut self, _ctx: &mut WidgetCtx<'_>, _control: &ControlState) -> ResourceState {
ResourceState::NONE
}
}