use std::sync::Arc;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, Key, Modifiers, MouseButton};
use crate::framebuffer::Framebuffer;
use crate::lcd_coverage::LcdBuffer;
use crate::geometry::{Point, Rect, Size};
use crate::gfx_ctx::GfxCtx;
use crate::layout_props::{HAnchor, Insets, VAnchor};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BackbufferMode {
Rgba,
LcdCoverage,
}
pub struct BackbufferCache {
pub pixels: Option<Arc<Vec<u8>>>,
pub lcd_alpha: Option<Arc<Vec<u8>>>,
pub width: u32,
pub height: u32,
pub dirty: bool,
pub theme_epoch: u64,
pub typography_epoch: u64,
}
impl BackbufferCache {
pub fn new() -> Self {
Self {
pixels: None, lcd_alpha: None,
width: 0, height: 0, dirty: true,
theme_epoch: 0, typography_epoch: 0,
}
}
pub fn invalidate(&mut self) { self.dirty = true; }
}
impl Default for BackbufferCache {
fn default() -> Self { Self::new() }
}
pub trait Widget {
fn bounds(&self) -> Rect;
fn set_bounds(&mut self, bounds: Rect);
fn children(&self) -> &[Box<dyn Widget>];
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>>;
fn layout(&mut self, available: Size) -> Size;
fn paint(&mut self, ctx: &mut dyn DrawCtx);
fn hit_test(&self, local_pos: Point) -> bool {
let b = self.bounds();
local_pos.x >= 0.0 && local_pos.x <= b.width
&& local_pos.y >= 0.0 && local_pos.y <= b.height
}
fn claims_pointer_exclusively(&self, _local_pos: Point) -> bool { false }
fn on_event(&mut self, event: &Event) -> EventResult;
fn is_focusable(&self) -> bool {
false
}
fn type_name(&self) -> &'static str {
"Widget"
}
fn id(&self) -> Option<&str> {
None
}
fn is_visible(&self) -> bool {
true
}
fn properties(&self) -> Vec<(&'static str, String)> {
vec![]
}
fn has_backbuffer(&self) -> bool {
false
}
fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
None
}
fn backbuffer_mode(&self) -> BackbufferMode {
BackbufferMode::Rgba
}
fn contributes_children_to_inspector(&self) -> bool {
true
}
fn show_in_inspector(&self) -> bool { true }
fn lcd_preference(&self) -> Option<bool> { None }
fn paint_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
let b = self.bounds();
Some((0.0, 0.0, b.width, b.height))
}
fn margin(&self) -> Insets { Insets::ZERO }
fn h_anchor(&self) -> HAnchor { HAnchor::FIT }
fn v_anchor(&self) -> VAnchor { VAnchor::FIT }
fn min_size(&self) -> Size { Size::ZERO }
fn max_size(&self) -> Size { Size::MAX }
fn enforce_integer_bounds(&self) -> bool {
crate::pixel_bounds::default_enforce_integer_bounds()
}
fn take_raise_request(&mut self) -> bool { false }
fn needs_paint(&self) -> bool {
if !self.is_visible() { return false; }
self.children().iter().any(|c| c.needs_paint())
}
fn next_paint_deadline(&self) -> Option<web_time::Instant> {
if !self.is_visible() { return None; }
let mut best: Option<web_time::Instant> = None;
for c in self.children() {
if let Some(t) = c.next_paint_deadline() {
best = Some(match best { Some(b) if b <= t => b, _ => t });
}
}
best
}
}
pub fn paint_subtree(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
if !widget.is_visible() { return; }
if widget.backbuffer_cache_mut().is_some() {
paint_subtree_backbuffered(widget, ctx);
} else {
paint_subtree_direct(widget, ctx);
}
}
fn paint_subtree_direct(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
paint_subtree_direct_inner(widget, ctx, true);
}
fn paint_subtree_direct_no_overlay(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
paint_subtree_direct_inner(widget, ctx, false);
}
fn paint_subtree_direct_inner(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
) {
let snap_this = widget.enforce_integer_bounds();
if snap_this {
ctx.save();
ctx.snap_to_pixel();
}
widget.paint(ctx);
let b = widget.bounds();
let (cx, cy, cw, ch) = widget.clip_children_rect()
.unwrap_or((0.0, 0.0, b.width, b.height));
ctx.save();
ctx.clip_rect(cx, cy, cw, ch);
let n = widget.children().len();
for i in 0..n {
let child_bounds = widget.children()[i].bounds();
let snap_to_pixel = widget.children()[i].enforce_integer_bounds();
ctx.save();
if snap_to_pixel {
ctx.translate(child_bounds.x.round(), child_bounds.y.round());
} else {
ctx.translate(child_bounds.x, child_bounds.y);
}
let child = &mut widget.children_mut()[i];
paint_subtree(child.as_mut(), ctx);
ctx.restore();
}
ctx.restore(); if include_overlay {
widget.paint_overlay(ctx);
}
if snap_this {
ctx.restore();
}
}
fn paint_subtree_backbuffered(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
ctx.save();
ctx.snap_to_pixel();
let b = widget.bounds();
let dps = crate::device_scale::device_scale().max(1e-6);
let w_phys = (b.width * dps).ceil().max(1.0) as u32;
let h_phys = (b.height * dps).ceil().max(1.0) as u32;
let w_logical = w_phys as f64 / dps;
let h_logical = h_phys as f64 / dps;
let mode = widget.backbuffer_mode();
let mode_is_lcd = matches!(mode, BackbufferMode::LcdCoverage);
let theme_epoch = crate::theme::current_visuals_epoch();
let typography_epoch = crate::font_settings::current_typography_epoch();
let (needs_raster, has_bitmap) = {
let cache = widget.backbuffer_cache_mut()
.expect("backbuffered widget must return Some from backbuffer_cache_mut");
let cache_is_lcd = cache.lcd_alpha.is_some();
let needs = cache.dirty
|| cache.pixels.is_none()
|| cache.width != w_phys
|| cache.height != h_phys
|| cache_is_lcd != mode_is_lcd
|| cache.theme_epoch != theme_epoch
|| cache.typography_epoch != typography_epoch;
(needs, cache.pixels.is_some())
};
if needs_raster {
let (pixels_bytes, lcd_alpha_bytes): (Vec<u8>, Option<Vec<u8>>) = match mode {
BackbufferMode::Rgba => {
let mut fb = Framebuffer::new(w_phys, h_phys);
{
let mut sub = GfxCtx::new(&mut fb);
sub.set_lcd_mode(false); if (dps - 1.0).abs() > 1e-6 {
sub.scale(dps, dps);
}
paint_subtree_direct_no_overlay(widget, &mut sub);
}
let mut pixels = fb.pixels_flipped();
crate::framebuffer::unpremultiply_rgba_inplace(&mut pixels);
(pixels, None)
}
BackbufferMode::LcdCoverage => {
let mut buf = LcdBuffer::new(w_phys, h_phys);
{
let mut sub = crate::lcd_gfx_ctx::LcdGfxCtx::new(&mut buf);
if (dps - 1.0).abs() > 1e-6 {
sub.scale(dps, dps);
}
paint_subtree_direct_no_overlay(widget, &mut sub);
}
(buf.color_plane_flipped(), Some(buf.alpha_plane_flipped()))
}
};
let pixels = Arc::new(pixels_bytes);
let lcd_alpha = lcd_alpha_bytes.map(Arc::new);
let cache = widget.backbuffer_cache_mut().unwrap();
cache.pixels = Some(Arc::clone(&pixels));
cache.lcd_alpha = lcd_alpha.as_ref().map(Arc::clone);
cache.width = w_phys;
cache.height = h_phys;
cache.dirty = false;
cache.theme_epoch = theme_epoch;
cache.typography_epoch = typography_epoch;
}
let cache = widget.backbuffer_cache_mut().unwrap();
let img_w = cache.width;
let img_h = cache.height;
match (cache.pixels.as_ref(), cache.lcd_alpha.as_ref()) {
(Some(color), Some(alpha)) => {
ctx.draw_lcd_backbuffer_arc(
color, alpha, img_w, img_h,
0.0, 0.0, w_logical, h_logical,
);
}
(Some(bmp), None) => {
ctx.draw_image_rgba_arc(bmp, img_w, img_h, 0.0, 0.0, w_logical, h_logical);
}
_ => {}
}
let _ = has_bitmap;
widget.paint_overlay(ctx);
ctx.restore(); }
pub fn hit_test_subtree(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
if !widget.is_visible() || !widget.hit_test(local_pos) {
return None;
}
if widget.claims_pointer_exclusively(local_pos) {
return Some(vec![]);
}
for (i, child) in widget.children().iter().enumerate().rev() {
let child_local = Point::new(
local_pos.x - child.bounds().x,
local_pos.y - child.bounds().y,
);
if let Some(mut sub_path) = hit_test_subtree(child.as_ref(), child_local) {
sub_path.insert(0, i);
return Some(sub_path);
}
}
Some(vec![]) }
pub fn dispatch_event(
root: &mut Box<dyn Widget>,
path: &[usize],
event: &Event,
pos_in_root: Point,
) -> EventResult {
if path.is_empty() {
return root.on_event(event);
}
let idx = path[0];
if idx >= root.children().len() {
return root.on_event(event);
}
let child_bounds = root.children()[idx].bounds();
let child_pos = Point::new(pos_in_root.x - child_bounds.x, pos_in_root.y - child_bounds.y);
let translated_event = translate_event(event, child_pos);
let child_result = dispatch_event(
&mut root.children_mut()[idx],
&path[1..],
&translated_event,
child_pos,
);
if child_result == EventResult::Consumed {
return EventResult::Consumed;
}
root.on_event(event)
}
fn translate_event(event: &Event, new_pos: Point) -> Event {
match event {
Event::MouseMove { .. } => Event::MouseMove { pos: new_pos },
Event::MouseDown { button, modifiers, .. } => Event::MouseDown {
pos: new_pos, button: *button, modifiers: *modifiers,
},
Event::MouseUp { button, modifiers, .. } => Event::MouseUp {
pos: new_pos, button: *button, modifiers: *modifiers,
},
Event::MouseWheel { delta_y, delta_x, .. } => Event::MouseWheel {
pos: new_pos, delta_y: *delta_y, delta_x: *delta_x,
},
other => other.clone(),
}
}
#[derive(Clone)]
pub struct InspectorNode {
pub type_name: &'static str,
pub screen_bounds: Rect,
pub depth: usize,
pub properties: Vec<(&'static str, String)>,
}
pub fn collect_inspector_nodes(
widget: &dyn Widget,
depth: usize,
screen_origin: Point,
out: &mut Vec<InspectorNode>,
) {
if !widget.is_visible() { return; }
if !widget.show_in_inspector() { return; }
let b = widget.bounds();
let abs = Rect::new(
screen_origin.x + b.x,
screen_origin.y + b.y,
b.width,
b.height,
);
let mut props = vec![
("backbuffer", if widget.has_backbuffer() { "true".to_string() }
else { "false".to_string() }),
];
props.extend(widget.properties());
out.push(InspectorNode {
type_name: widget.type_name(),
screen_bounds: abs,
depth,
properties: props,
});
if !widget.contributes_children_to_inspector() { return; }
let child_origin = Point::new(abs.x, abs.y);
for child in widget.children() {
collect_inspector_nodes(child.as_ref(), depth + 1, child_origin, out);
}
}
fn collect_focusable(widget: &dyn Widget, current_path: &mut Vec<usize>, out: &mut Vec<Vec<usize>>) {
if widget.is_focusable() {
out.push(current_path.clone());
}
for (i, child) in widget.children().iter().enumerate() {
current_path.push(i);
collect_focusable(child.as_ref(), current_path, out);
current_path.pop();
}
}
fn widget_at_path<'a>(root: &'a mut Box<dyn Widget>, path: &[usize]) -> &'a mut dyn Widget {
if path.is_empty() {
return root.as_mut();
}
let idx = path[0];
widget_at_path(&mut root.children_mut()[idx], &path[1..])
}
pub struct App {
root: Box<dyn Widget>,
focus: Option<Vec<usize>>,
hovered: Option<Vec<usize>>,
captured: Option<Vec<usize>>,
viewport_height: f64,
global_key_handler: Option<Box<dyn FnMut(Key, Modifiers) -> bool>>,
touch_state: crate::touch_state::TouchState,
}
impl App {
pub fn new(root: Box<dyn Widget>) -> Self {
Self {
root,
focus: None,
hovered: None,
captured: None,
viewport_height: 1.0,
global_key_handler: None,
touch_state: crate::touch_state::TouchState::new(),
}
}
pub fn set_global_key_handler(&mut self, handler: impl FnMut(Key, Modifiers) -> bool + 'static) {
self.global_key_handler = Some(Box::new(handler));
}
pub fn layout(&mut self, viewport: Size) {
let scale = crate::device_scale::device_scale().max(1e-6);
let logical = Size::new(viewport.width / scale, viewport.height / scale);
self.viewport_height = logical.height;
self.root.set_bounds(Rect::new(0.0, 0.0, logical.width, logical.height));
self.root.layout(logical);
}
pub fn paint(&mut self, ctx: &mut dyn DrawCtx) {
crate::animation::clear_tick();
self.touch_state.update_gesture();
crate::touch_state::set_current(self.touch_state.current());
let scale = crate::device_scale::device_scale();
if (scale - 1.0).abs() > 1e-6 {
ctx.save();
ctx.scale(scale, scale);
paint_subtree(self.root.as_mut(), ctx);
ctx.restore();
} else {
paint_subtree(self.root.as_mut(), ctx);
}
}
pub fn wants_animation_tick(&self) -> bool {
self.root.needs_paint() || crate::animation::wants_tick()
}
pub fn next_paint_deadline(&self) -> Option<web_time::Instant> {
self.root.next_paint_deadline()
}
pub fn on_mouse_move(&mut self, screen_x: f64, screen_y: f64) {
crate::cursor::reset_cursor_icon();
let pos = self.flip_y(screen_x, screen_y);
self.dispatch_mouse_move(pos);
}
pub fn on_mouse_down(&mut self, screen_x: f64, screen_y: f64, button: MouseButton, mods: Modifiers) {
let pos = self.flip_y(screen_x, screen_y);
let hit = self.compute_hit(pos);
if let Some(ref path) = hit {
let w = widget_at_path(&mut self.root, path);
if w.is_focusable() {
self.set_focus(Some(path.clone()));
} else {
self.set_focus(None);
}
} else {
self.set_focus(None);
}
let event = Event::MouseDown { pos, button, modifiers: mods };
if let Some(mut path) = hit {
let result = dispatch_event(&mut self.root, &path, &event, pos);
if result == EventResult::Consumed {
self.maybe_bring_to_front(&mut path);
self.captured = Some(path);
}
}
}
pub fn on_mouse_up(&mut self, screen_x: f64, screen_y: f64, button: MouseButton, mods: Modifiers) {
let pos = self.flip_y(screen_x, screen_y);
let event = Event::MouseUp { pos, button, modifiers: mods };
if let Some(path) = self.captured.take() {
dispatch_event(&mut self.root, &path, &event, pos);
} else {
let hit = self.compute_hit(pos);
if let Some(path) = hit {
dispatch_event(&mut self.root, &path, &event, pos);
}
}
}
pub fn on_key_down(&mut self, key: Key, mods: Modifiers) {
if key == Key::Tab {
self.advance_focus(!mods.shift);
return;
}
if let Some(ref mut handler) = self.global_key_handler {
if handler(key.clone(), mods) {
return;
}
}
let event = Event::KeyDown { key, modifiers: mods };
if let Some(path) = self.focus.clone() {
dispatch_event(&mut self.root, &path, &event, Point::ORIGIN);
}
}
pub fn on_key_up(&mut self, key: Key, mods: Modifiers) {
let event = Event::KeyUp { key, modifiers: mods };
if let Some(path) = self.focus.clone() {
dispatch_event(&mut self.root, &path, &event, Point::ORIGIN);
}
}
pub fn on_mouse_wheel(&mut self, screen_x: f64, screen_y: f64, delta_y: f64) {
self.on_mouse_wheel_xy(screen_x, screen_y, 0.0, delta_y);
}
pub fn on_mouse_wheel_xy(
&mut self,
screen_x: f64, screen_y: f64,
delta_x: f64, delta_y: f64,
) {
let pos = self.flip_y(screen_x, screen_y);
let hit = self.compute_hit(pos);
let event = Event::MouseWheel { pos, delta_y, delta_x };
if let Some(path) = hit {
dispatch_event(&mut self.root, &path, &event, pos);
}
}
pub fn collect_inspector_nodes(&self) -> Vec<InspectorNode> {
let mut out = Vec::new();
collect_inspector_nodes(self.root.as_ref(), 0, Point::ORIGIN, &mut out);
out
}
pub fn dump_tree_json(&self) -> String {
let nodes = self.collect_inspector_nodes();
let mut s = String::from("[\n");
for (i, n) in nodes.iter().enumerate() {
let props_json = n.properties.iter()
.map(|(k, v)| format!("{:?}: {:?}", k, v))
.collect::<Vec<_>>()
.join(", ");
s.push_str(&format!(
" {{\"type\":{:?},\"depth\":{},\"x\":{:.2},\"y\":{:.2},\"w\":{:.2},\"h\":{:.2},\"props\":{{{}}}}}",
n.type_name, n.depth,
n.screen_bounds.x, n.screen_bounds.y,
n.screen_bounds.width, n.screen_bounds.height,
props_json,
));
if i + 1 < nodes.len() { s.push(','); }
s.push('\n');
}
s.push(']');
s
}
pub fn has_focus(&self) -> bool { self.focus.is_some() }
pub fn on_mouse_leave(&mut self) {
crate::cursor::reset_cursor_icon();
self.dispatch_mouse_move(Point::new(-1.0, -1.0));
}
pub fn on_touch_start(
&mut self,
device: crate::touch_state::TouchDeviceId,
id: crate::touch_state::TouchId,
screen_x: f64, screen_y: f64,
force: Option<f32>,
) {
let pos = self.flip_y(screen_x, screen_y);
self.touch_state.on_start(device, id, pos, force);
}
pub fn on_touch_move(
&mut self,
device: crate::touch_state::TouchDeviceId,
id: crate::touch_state::TouchId,
screen_x: f64, screen_y: f64,
force: Option<f32>,
) {
let pos = self.flip_y(screen_x, screen_y);
self.touch_state.on_move(device, id, pos, force);
}
pub fn on_touch_end(
&mut self,
device: crate::touch_state::TouchDeviceId,
id: crate::touch_state::TouchId,
) {
self.touch_state.on_end_or_cancel(device, id);
}
pub fn on_touch_cancel(
&mut self,
device: crate::touch_state::TouchDeviceId,
id: crate::touch_state::TouchId,
) {
self.touch_state.on_end_or_cancel(device, id);
}
pub fn active_touch_count(&self) -> usize {
self.touch_state.active_count()
}
fn maybe_bring_to_front(&mut self, clicked_path: &mut Vec<usize>) {
let mut node: &dyn Widget = self.root.as_ref();
let mut window_info: Option<(Vec<usize>, usize)> = None; for (depth, &idx) in clicked_path.iter().enumerate() {
let children = node.children();
if idx >= children.len() { break; }
node = &*children[idx];
if node.type_name() == "Window" {
window_info = Some((clicked_path[..depth].to_vec(), idx));
}
}
let (parent_path, win_idx) = match window_info { Some(x) => x, None => return };
let n = {
let parent = widget_at_path(&mut self.root, &parent_path);
parent.children().len()
};
if win_idx >= n - 1 { return; }
{
let parent = widget_at_path(&mut self.root, &parent_path);
let child = parent.children_mut().remove(win_idx);
parent.children_mut().push(child);
}
let new_idx = n - 1;
let depth = parent_path.len();
fn shift_path(p: &mut Vec<usize>, depth: usize, old: usize, new: usize) {
if p.len() > depth {
let i = p[depth];
if i == old {
p[depth] = new;
} else if i > old && i <= new {
p[depth] -= 1;
}
}
}
shift_path(clicked_path, depth, win_idx, new_idx);
if let Some(ref mut p) = self.focus { shift_path(p, depth, win_idx, new_idx); }
if let Some(ref mut p) = self.hovered { shift_path(p, depth, win_idx, new_idx); }
if let Some(ref mut p) = self.captured { shift_path(p, depth, win_idx, new_idx); }
}
#[inline]
fn flip_y(&self, x: f64, y_down: f64) -> Point {
let scale = crate::device_scale::device_scale().max(1e-6);
let lx = x / scale;
let ly_down = y_down / scale;
Point::new(lx, self.viewport_height - ly_down)
}
fn compute_hit(&self, pos: Point) -> Option<Vec<usize>> {
hit_test_subtree(self.root.as_ref(), pos)
}
fn dispatch_mouse_move(&mut self, pos: Point) {
let new_hit = self.compute_hit(pos);
if new_hit != self.hovered {
if let Some(old_path) = self.hovered.take() {
let is_captured = self.captured.as_ref() == Some(&old_path);
if !is_captured {
let clear = Event::MouseMove { pos: Point::new(-1.0, -1.0) };
dispatch_event(&mut self.root, &old_path, &clear, Point::new(-1.0, -1.0));
}
}
self.hovered = new_hit.clone();
}
let event = Event::MouseMove { pos };
if let Some(ref cap_path) = self.captured.clone() {
dispatch_event(&mut self.root, cap_path, &event, pos);
} else if let Some(path) = new_hit {
dispatch_event(&mut self.root, &path, &event, pos);
}
}
fn set_focus(&mut self, new_path: Option<Vec<usize>>) {
if self.focus == new_path {
return;
}
if let Some(old) = self.focus.take() {
dispatch_event(&mut self.root, &old, &Event::FocusLost, Point::ORIGIN);
}
self.focus = new_path.clone();
if let Some(new) = new_path {
dispatch_event(&mut self.root, &new, &Event::FocusGained, Point::ORIGIN);
}
}
fn advance_focus(&mut self, forward: bool) {
let mut all: Vec<Vec<usize>> = Vec::new();
collect_focusable(self.root.as_ref(), &mut vec![], &mut all);
if all.is_empty() {
return;
}
let current_idx = self.focus.as_ref()
.and_then(|f| all.iter().position(|p| p == f));
let next_idx = match current_idx {
None => if forward { 0 } else { all.len() - 1 },
Some(i) => {
if forward {
(i + 1) % all.len()
} else {
if i == 0 { all.len() - 1 } else { i - 1 }
}
}
};
let next_path = all[next_idx].clone();
self.set_focus(Some(next_path));
}
}