use std::cell::Cell;
use std::rc::Rc;
use web_time::Instant;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::widget::Widget;
mod events;
mod transform;
pub use transform::SceneTransform;
pub const DEFAULT_ZOOM_RANGE: (f64, f64) = (0.1, 2.0);
const DBL_CLICK_MS: u128 = 400;
const MAX_CLICK_DIST: f64 = 6.0;
const ZOOM_SENSITIVITY: f64 = 0.1;
pub struct Scene {
bounds: Rect,
base: WidgetBase,
children: Vec<Box<dyn Widget>>,
transform: SceneTransform,
zoom_range: (f64, f64),
content_size: Option<Size>,
default_scene_rect: Option<Rect>,
user_interacted: bool,
scene_rect_cell: Option<Rc<Cell<Rect>>>,
reset_cell: Option<Rc<Cell<bool>>>,
panning: bool,
pan_last: Point,
pan_press: Point,
pan_moved: bool,
pan_is_left: bool,
pan_press_epoch: u64,
last_bg_click: Option<Instant>,
last_bg_click_epoch: Option<u64>,
}
impl Scene {
pub fn new(content: Box<dyn Widget>) -> Self {
Self {
bounds: Rect::default(),
base: WidgetBase::new(),
children: vec![content],
transform: SceneTransform::identity(),
zoom_range: DEFAULT_ZOOM_RANGE,
content_size: None,
default_scene_rect: None,
user_interacted: false,
scene_rect_cell: None,
reset_cell: None,
panning: false,
pan_last: Point::ORIGIN,
pan_press: Point::ORIGIN,
pan_moved: false,
pan_is_left: false,
pan_press_epoch: 0,
last_bg_click: None,
last_bg_click_epoch: None,
}
}
pub(super) fn content(&self) -> &dyn Widget {
self.children[0].as_ref()
}
pub(super) fn content_mut(&mut self) -> &mut dyn Widget {
self.children[0].as_mut()
}
pub fn with_zoom_range(mut self, min: f64, max: f64) -> Self {
self.zoom_range = (min, max);
self
}
pub fn with_content_size(mut self, size: Size) -> Self {
self.content_size = Some(size);
self
}
pub fn with_default_scene_rect(mut self, rect: Rect) -> Self {
self.default_scene_rect = Some(rect);
self
}
pub fn with_scene_rect_cell(mut self, cell: Rc<Cell<Rect>>) -> Self {
self.scene_rect_cell = Some(cell);
self
}
pub fn with_reset_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
self.reset_cell = Some(cell);
self
}
pub fn with_margin(mut self, m: Insets) -> Self {
self.base.margin = m;
self
}
pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
self.base.h_anchor = h;
self
}
pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
self.base.v_anchor = v;
self
}
pub fn with_min_size(mut self, s: Size) -> Self {
self.base.min_size = s;
self
}
pub fn with_max_size(mut self, s: Size) -> Self {
self.base.max_size = s;
self
}
pub fn scene_rect(&self) -> Rect {
self.transform
.visible_scene_rect(Size::new(self.bounds.width, self.bounds.height))
}
pub fn transform(&self) -> SceneTransform {
self.transform
}
pub fn reset_view(&mut self) {
self.user_interacted = false;
self.fit_to_default();
self.publish_scene_rect();
crate::animation::request_draw();
}
fn fit_to_default(&mut self) {
let container = Size::new(self.bounds.width, self.bounds.height);
let cb = self.content().bounds();
let target = self
.default_scene_rect
.unwrap_or_else(|| Rect::new(0.0, 0.0, cb.width, cb.height));
if container.width > 0.0
&& container.height > 0.0
&& target.width > 0.0
&& target.height > 0.0
{
self.transform = SceneTransform::fit(target, container, self.zoom_range);
}
}
fn publish_scene_rect(&self) {
if let Some(cell) = &self.scene_rect_cell {
cell.set(self.scene_rect());
}
}
}
impl Widget for Scene {
fn type_name(&self) -> &'static str {
"Scene"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn child_transform(&self) -> Option<crate::TransAffine> {
Some(self.transform.to_affine())
}
fn claims_pointer_exclusively(&self, _local_pos: Point) -> bool {
self.panning
}
fn margin(&self) -> Insets {
self.base.margin
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
Some(&mut self.base)
}
fn h_anchor(&self) -> HAnchor {
self.base.h_anchor
}
fn v_anchor(&self) -> VAnchor {
self.base.v_anchor
}
fn min_size(&self) -> Size {
self.base.min_size
}
fn max_size(&self) -> Size {
self.base.max_size
}
fn enforce_integer_bounds(&self) -> bool {
false
}
fn layout(&mut self, available: Size) -> Size {
if let Some(cell) = &self.reset_cell {
if cell.get() {
cell.set(false);
self.user_interacted = false;
}
}
self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
let content_avail = self.content_size.unwrap_or(available);
let sz = self.content_mut().layout(content_avail);
self.content_mut()
.set_bounds(Rect::new(0.0, 0.0, sz.width, sz.height));
if !self.user_interacted {
self.fit_to_default();
}
self.publish_scene_rect();
available
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
let v = ctx.visuals();
let w = self.bounds.width;
let h = self.bounds.height;
ctx.set_fill_color(v.bg_color);
ctx.begin_path();
ctx.rect(0.0, 0.0, w, h);
ctx.fill();
}
fn on_event(&mut self, event: &Event) -> EventResult {
self.handle_event(event)
}
fn properties(&self) -> Vec<(&'static str, String)> {
let r = self.scene_rect();
vec![
("zoom", format!("{:.3}", self.transform.zoom)),
(
"scene_rect",
format!(
"[{:.1}, {:.1}, {:.1}, {:.1}]",
r.x, r.y, r.width, r.height
),
),
(
"zoom_range",
format!("{:.2}..={:.2}", self.zoom_range.0, self.zoom_range.1),
),
]
}
}