use crate::animation::AnimationCoordinator;
use crate::input::InputState;
use crate::layout::tree::LayoutTree;
use crate::state::StateRegistry;
use crate::types::{Rect, WidgetId, WidgetState, ScrollState};
use crate::widgets;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ButtonResponse {
pub clicked: bool,
pub hovered: bool,
pub pressed: bool,
pub state: WidgetState,
pub rect: Rect,
}
pub struct Context {
pub input: InputState,
pub layout: LayoutTree,
pub registry: StateRegistry,
pub animations: AnimationCoordinator,
pub time: f64,
}
impl Context {
pub fn new(root_node: crate::layout::types::LayoutNode) -> Self {
Self {
input: InputState::default(),
layout: LayoutTree::new(root_node),
registry: StateRegistry::new(),
animations: AnimationCoordinator::new(),
time: 0.0,
}
}
pub fn begin_frame(&mut self, input: InputState, viewport: Rect) {
self.input = input;
self.time = self.input.time;
self.animations.update(self.time);
self.layout.compute(viewport);
}
pub fn state<T: 'static + Send + Sync + Default>(&mut self, id: impl Into<WidgetId>) -> &mut T {
self.registry.get_or_insert_with(id.into(), T::default)
}
pub fn widget_rect(&self, id: &WidgetId) -> Rect {
self.layout.get_rect(id).unwrap_or_default()
}
pub fn button(&mut self, id: impl Into<WidgetId>) -> ButtonResponse {
let id = id.into();
let rect = self.widget_rect(&id);
let is_hovered = self.input.is_hovered(&rect);
let clicked = is_hovered && self.input.is_clicked();
let state = if clicked {
WidgetState::Pressed
} else if is_hovered {
WidgetState::Hovered
} else {
WidgetState::Normal
};
ButtonResponse {
clicked,
hovered: is_hovered,
pressed: clicked,
state,
rect,
}
}
pub fn checkbox(&mut self, id: impl Into<WidgetId>, checked: bool) -> widgets::checkbox::CheckboxResponse {
let id = id.into();
let rect = self.widget_rect(&id);
let is_hovered = self.input.is_hovered(&rect);
let clicked = is_hovered && self.input.is_clicked();
let state = if clicked {
WidgetState::Pressed
} else if is_hovered {
WidgetState::Hovered
} else {
WidgetState::Normal
};
let toggled = clicked;
let new_checked = if toggled { !checked } else { checked };
widgets::checkbox::CheckboxResponse {
toggled,
new_checked,
hovered: is_hovered,
state,
rect,
}
}
pub fn scroll_area(&mut self, id: impl Into<WidgetId>, content_height: f64) -> (Rect, ScrollState) {
let id = id.into();
let viewport = self.widget_rect(&id);
let is_hovered = self.input.is_hovered(&viewport);
let dt = self.input.dt;
let scroll_delta_y = if is_hovered { self.input.scroll_delta.1 } else { 0.0 };
let state = self.state::<ScrollState>(id);
state.content_size = content_height;
if scroll_delta_y != 0.0 {
state.velocity -= scroll_delta_y * 1500.0;
}
state.offset += state.velocity * dt;
state.velocity *= 0.90;
if state.velocity.abs() < 0.1 {
state.velocity = 0.0;
}
let max_scroll = (content_height - viewport.height).max(0.0);
if state.offset < 0.0 {
state.offset = 0.0;
state.velocity = 0.0;
} else if state.offset > max_scroll {
state.offset = max_scroll;
state.velocity = 0.0;
}
(viewport, state.clone())
}
pub fn icon_button(
&mut self,
id: impl Into<WidgetId>,
) -> widgets::icon_button::IconButtonResponse {
let id = id.into();
let rect = self.widget_rect(&id);
let is_hovered = self.input.is_hovered(&rect);
let clicked = is_hovered && self.input.is_clicked();
let state = if is_hovered {
if self.input.is_mouse_down() {
WidgetState::Pressed
} else {
WidgetState::Hovered
}
} else {
WidgetState::Normal
};
if clicked {
println!("[UZOR Core] Button '{:?}' CLICKED at rect {:?}", id, rect);
} else if is_hovered {
println!("[UZOR Core] Button '{:?}' HOVERED at rect {:?}", id, rect);
}
widgets::icon_button::IconButtonResponse {
clicked,
hovered: is_hovered,
state,
}
}
}