use std::panic::Location;
use std::path::PathBuf;
use std::time::Duration;
use accesskit::ActionRequest;
use keyboard_types::KeyboardEvent;
use kurbo::{Point, Rect, RoundedRect, Size, Vec2};
use parley::{AlignmentOptions, Layout};
use vello::Scene;
use crate::prelude::*;
use crate::{layout, text};
#[derive(Debug, Default, Copy, Clone)]
pub struct DispatchInfo {
pub callback_count: u32,
pub bubbling_stopped: bool,
pub stop_window_close: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum On {
AccessibilityAction,
AnimationFrame,
Blur,
Change,
Command,
Create,
Destroy,
FileDialog,
Focus,
Keyboard,
PointerDown,
PointerEnter,
PointerLeave,
PointerMove,
PointerUp,
PointerWheel,
Timer,
WindowBlur,
WindowClose,
WindowFocus,
}
impl On {
pub fn is_pointer(&self) -> bool {
matches!(self, On::PointerDown | On::PointerUp | On::PointerMove | On::PointerEnter | On::PointerLeave | On::PointerWheel)
}
}
pub struct MeasureCtx<'a> {
pub style: &'a Style,
pub max_size: Option<Size>,
}
pub struct CanvasCtx<'a> {
pub did_layout: bool,
pub is_active: bool,
pub is_enabled: bool,
pub is_focused: bool,
pub perf_info: &'a PerfInfo,
pub rect: &'a RoundedRect,
pub scene: &'a mut Scene,
pub style: &'a Style,
pub translation_map: TranslationMap,
}
impl<'a> CanvasCtx<'a> {
#[inline]
pub fn padding_box(&self) -> Rect {
layout::padding_box(self.style, self.rect)
}
#[inline]
pub fn max_content_width(&self) -> f32 {
layout::max_content_width(self.style, self.rect)
}
#[inline]
pub fn draw_text(&mut self, text: &str) {
let max_width = layout::max_content_width(self.style, self.rect);
let mut layout = text::layout_text(&self.style.get_font_layout_style(), Some(max_width), text);
let origin = layout::align_and_position_text(self.style, self.rect, &mut layout);
text::draw_text(self.scene, self.style, origin, &layout);
}
#[inline]
pub fn draw_text_at_origin(&mut self, origin: Point, max_width: impl Into<Option<f32>>, text: &str) {
let max_width = max_width.into();
let mut layout = text::layout_text(&self.style.get_font_layout_style(), max_width, text);
layout.align(max_width, self.style.text_align.into(), AlignmentOptions::default());
text::draw_text(self.scene, self.style, origin, &layout);
}
#[inline]
pub fn draw_text_layout(&mut self, origin: Point, layout: &Layout<[u8; 4]>) {
text::draw_text(self.scene, self.style, origin, layout);
}
}
#[derive(Clone, Debug)]
pub enum FileDialogResponse {
Opened(Vec<PathBuf>),
Saved(PathBuf),
Cancelled,
}
impl FileDialogResponse {
pub fn path(&self) -> Option<&PathBuf> {
match self {
FileDialogResponse::Opened(paths) => paths.first(),
FileDialogResponse::Saved(path) => Some(path),
FileDialogResponse::Cancelled => None,
}
}
pub fn paths(&self) -> Vec<PathBuf> {
match self {
FileDialogResponse::Opened(paths) => paths.clone(),
FileDialogResponse::Saved(path) => vec![path.clone()],
FileDialogResponse::Cancelled => Vec::new(),
}
}
pub fn is_opened(&self) -> bool {
matches!(self, FileDialogResponse::Opened { .. })
}
pub fn is_saved(&self) -> bool {
matches!(self, FileDialogResponse::Saved { .. })
}
pub fn is_cancelled(&self) -> bool {
matches!(self, FileDialogResponse::Cancelled)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CommandId(pub u32);
impl From<u32> for CommandId {
fn from(value: u32) -> Self {
Self(value)
}
}
#[derive(Clone, Debug)]
pub enum EventInfo {
None,
Pointer(PointerEvent),
Keyboard(KeyboardEvent),
Animation(Duration),
File(FileDialogResponse),
Command(CommandId),
AccessibilityAction(ActionRequest),
}
#[derive(Copy, Clone)]
pub(crate) enum FocusDirection {
None,
Next,
Previous,
}
#[derive(Default, Debug, Clone, Copy)]
pub struct PerfInfo {
pub frame_number: u64,
pub node_count: usize,
pub build_time: Duration,
pub style_time: Duration,
pub layout_time: Duration,
pub layout_twice: bool,
pub scene_time: Duration,
pub paint_time: Duration,
}
impl PerfInfo {
#[inline]
pub fn total_time(&self) -> Duration {
self.build_time + self.style_time + self.layout_time + self.scene_time + self.paint_time
}
#[inline]
pub fn cpu_time(&self) -> Duration {
self.build_time + self.style_time + self.layout_time + self.scene_time
}
#[inline]
pub fn gpu_time(&self) -> Duration {
self.paint_time
}
}
pub struct EventCtx<'a, H> {
pub(crate) active_node: Option<NodeId>,
pub(crate) captured_node: Option<NodeId>,
pub(crate) emit_change: bool,
pub(crate) event_type: On,
pub(crate) focus_direction: FocusDirection,
pub(crate) focused_node: Option<NodeId>,
pub(crate) handle: &'a H,
pub(crate) id: Option<NodeId>,
pub(crate) idx: usize,
pub(crate) info: EventInfo,
pub(crate) is_enabled: bool,
pub(crate) perf_info: &'a PerfInfo,
pub(crate) pointer_delta: Option<Vec2>,
pub(crate) rect: &'a RoundedRect,
pub(crate) stop_bubbling: bool,
pub(crate) stop_window_close: bool,
pub(crate) style: &'a Style,
pub(crate) translation_map: TranslationMap,
pub(crate) viewport_size: Size,
}
impl<'a, H> EventCtx<'a, H> {
#[inline]
pub fn platform(&self) -> &H {
self.handle
}
#[inline]
pub fn set_active(&mut self, id: Option<NodeId>) {
self.active_node = id;
}
#[inline]
pub fn is_active(&self) -> bool {
self.id.is_some() && self.id == self.active_node
}
#[inline]
pub fn is_enabled(&self) -> bool {
self.is_enabled
}
#[inline]
pub fn set_focus(&mut self, id: Option<NodeId>) {
self.focused_node = id;
}
#[inline]
pub fn focus_next(&mut self) {
self.focus_direction = FocusDirection::Next;
}
#[inline]
pub fn focus_previous(&mut self) {
self.focus_direction = FocusDirection::Previous;
}
#[inline]
pub fn is_focused(&self) -> bool {
self.id.is_some() && self.id == self.focused_node
}
#[inline]
pub fn viewport_size(&self) -> Size {
self.viewport_size
}
#[inline]
pub fn style(&self) -> &Style {
self.style
}
#[inline]
pub fn rect(&self) -> &RoundedRect {
self.rect
}
#[inline]
#[track_caller]
pub fn id(&self) -> Option<NodeId> {
if cfg!(debug_assertions) && self.id.is_none() {
let location = Location::caller();
log::error!("id() must be called on a node with an id: {location}");
}
self.id
}
#[inline]
pub fn event_type(&self) -> On {
self.event_type
}
#[inline]
pub fn pointer_delta(&self) -> Option<Vec2> {
self.pointer_delta
}
#[inline]
pub fn perf_info(&self) -> &PerfInfo {
self.perf_info
}
#[inline]
pub fn get_translation_map(&self) -> TranslationMap {
self.translation_map.clone()
}
#[inline]
#[track_caller]
pub fn begin_pointer_capture(&mut self) {
if cfg!(debug_assertions) && self.id.is_none() {
let location = Location::caller();
log::error!("begin_pointer_capture() must be called on a node with an id: {location}");
}
self.captured_node = self.id;
}
#[inline]
pub fn end_pointer_capture(&mut self) {
self.captured_node = None;
}
#[inline]
pub fn is_pointer_captured(&self) -> bool {
self.id.is_some() && self.id == self.captured_node
}
#[inline]
#[track_caller]
pub fn stop_propagation(&mut self) {
if cfg!(debug_assertions) && !self.event_type.is_pointer() {
let location = Location::caller();
log::error!("stop_propagation() must be called from a pointer event: {location}");
}
self.stop_bubbling = true;
}
#[inline]
pub fn emit_change(&mut self) {
self.emit_change = true;
}
#[inline]
#[track_caller]
pub fn stop_window_close(&mut self) {
if cfg!(debug_assertions) {
let location = Location::caller();
if self.event_type != On::WindowClose {
log::error!("stop_window_close() must be called from an On::WindowClose handler: {location}");
} else if self.idx != 0 {
log::error!("stop_window_close() must be called from the root node: {location}");
}
}
self.stop_window_close = true;
}
#[inline]
pub fn pointer(&self) -> Option<&PointerEvent> {
if let EventInfo::Pointer(event) = &self.info {
Some(event)
} else {
None
}
}
#[inline]
pub fn local_pointer_pos(&self) -> Option<Point> {
if let EventInfo::Pointer(event) = &self.info {
let vec = event.viewport_pos - self.rect.origin();
Some(Point::new(vec.x, vec.y))
} else {
None
}
}
#[inline]
pub fn keyboard(&self) -> Option<&KeyboardEvent> {
if let EventInfo::Keyboard(event) = &self.info {
Some(event)
} else {
None
}
}
#[inline]
pub fn dt(&self) -> Option<&Duration> {
if let EventInfo::Animation(dt) = &self.info {
Some(dt)
} else {
None
}
}
#[inline]
pub fn file_dialog_response(&self) -> Option<&FileDialogResponse> {
if let EventInfo::File(file) = &self.info {
Some(file)
} else {
None
}
}
#[inline]
pub fn command_id(&self) -> Option<CommandId> {
if let EventInfo::Command(cmd) = &self.info {
Some(*cmd)
} else {
None
}
}
#[inline]
pub fn action_request(&self) -> Option<&ActionRequest> {
if let EventInfo::AccessibilityAction(req) = &self.info {
Some(req)
} else {
None
}
}
#[inline]
pub fn info(&self) -> &EventInfo {
&self.info
}
#[inline]
pub fn padding_box(&self) -> Rect {
layout::padding_box(self.style, self.rect)
}
#[inline]
pub fn max_content_width(&self) -> f32 {
layout::max_content_width(self.style, self.rect)
}
}
pub struct AccessibilityCtx<'a> {
pub id: NodeId,
pub text: Option<&'a UIString>,
pub translation_map: TranslationMap,
pub node: &'a mut accesskit::Node,
}