use crate::Bounds;
#[derive(Debug, Clone)]
pub struct ContentEvent {
pub event_type: EventType,
pub position: Option<(usize, usize)>,
pub zone_id: Option<String>,
pub data: EventData,
pub timestamp: std::time::SystemTime,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventType {
Click,
DoubleClick,
Hover,
MouseMove,
KeyPress,
Focus,
Blur,
Scroll,
Resize,
BoxResize,
TitleChange,
Custom(String),
}
#[derive(Debug, Clone, Default)]
pub struct EventData {
pub mouse_button: Option<MouseButton>,
pub key: Option<KeyInfo>,
pub scroll: Option<ScrollInfo>,
pub size: Option<(usize, usize)>,
pub mouse_move: Option<MouseMoveInfo>,
pub hover: Option<HoverInfo>,
pub box_resize: Option<BoxResizeInfo>,
pub title_change: Option<TitleChangeInfo>,
pub custom_data: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MouseButton {
Left,
Right,
Middle,
WheelUp,
WheelDown,
}
#[derive(Debug, Clone)]
pub struct KeyInfo {
pub key: String,
pub modifiers: Vec<KeyModifier>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum KeyModifier {
Ctrl,
Alt,
Shift,
Meta,
}
#[derive(Debug, Clone)]
pub struct ScrollInfo {
pub direction: ScrollDirection,
pub amount: i32,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ScrollDirection {
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone)]
pub struct MouseMoveInfo {
pub from_position: Option<(usize, usize)>,
pub to_position: (usize, usize),
pub delta: (i32, i32),
pub is_dragging: bool,
pub drag_button: Option<MouseButton>,
}
#[derive(Debug, Clone)]
pub struct HoverInfo {
pub state: HoverState,
pub previous_zone: Option<String>,
pub current_zone: Option<String>,
pub hover_duration: Option<std::time::Duration>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HoverState {
Enter,
Leave,
Move,
}
#[derive(Debug, Clone)]
pub struct BoxResizeInfo {
pub resize_type: BoxResizeType,
pub original_bounds: (usize, usize, usize, usize), pub new_bounds: (usize, usize, usize, usize), pub anchor: ResizeAnchor,
pub state: ResizeState,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BoxResizeType {
Interactive,
Programmatic,
Terminal,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResizeAnchor {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Top,
Bottom,
Left,
Right,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResizeState {
Started,
InProgress,
Completed,
Cancelled,
}
#[derive(Debug, Clone)]
pub struct TitleChangeInfo {
pub old_title: Option<String>,
pub new_title: String,
pub source: TitleChangeSource,
pub persist: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TitleChangeSource {
User,
PTY,
Script,
API,
System,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventResult {
Handled,
NotHandled,
HandledContinue,
StateChanged,
}
impl ContentEvent {
pub fn new(
event_type: EventType,
position: Option<(usize, usize)>,
zone_id: Option<String>,
) -> Self {
Self {
event_type,
position,
zone_id,
data: EventData::default(),
timestamp: std::time::SystemTime::now(),
}
}
pub fn new_click(position: Option<(usize, usize)>, zone_id: Option<String>) -> Self {
let mut event = Self::new(EventType::Click, position, zone_id);
event.data.mouse_button = Some(MouseButton::Left);
event
}
pub fn new_click_with_button(
position: Option<(usize, usize)>,
zone_id: Option<String>,
button: MouseButton,
) -> Self {
let mut event = Self::new(EventType::Click, position, zone_id);
event.data.mouse_button = Some(button);
event
}
pub fn new_hover(position: (usize, usize), zone_id: Option<String>) -> Self {
Self::new(EventType::Hover, Some(position), zone_id)
}
pub fn new_keypress(key: String, modifiers: Vec<KeyModifier>, zone_id: Option<String>) -> Self {
let mut event = Self::new(EventType::KeyPress, None, zone_id);
event.data.key = Some(KeyInfo { key, modifiers });
event
}
pub fn new_scroll(direction: ScrollDirection, amount: i32, zone_id: Option<String>) -> Self {
let mut event = Self::new(EventType::Scroll, None, zone_id);
event.data.scroll = Some(ScrollInfo { direction, amount });
event
}
pub fn new_focus(zone_id: Option<String>) -> Self {
Self::new(EventType::Focus, None, zone_id)
}
pub fn new_blur(zone_id: Option<String>) -> Self {
Self::new(EventType::Blur, None, zone_id)
}
pub fn new_resize(new_size: (usize, usize)) -> Self {
let mut event = Self::new(EventType::Resize, None, None);
event.data.size = Some(new_size);
event
}
pub fn new_custom(name: String, data: Option<String>, zone_id: Option<String>) -> Self {
let mut event = Self::new(EventType::Custom(name), None, zone_id);
event.data.custom_data = data;
event
}
pub fn new_mouse_move(
from_pos: Option<(usize, usize)>,
to_pos: (usize, usize),
zone_id: Option<String>,
) -> Self {
let mut event = Self::new(EventType::MouseMove, Some(to_pos), zone_id);
let delta = if let Some(from) = from_pos {
(
to_pos.0 as i32 - from.0 as i32,
to_pos.1 as i32 - from.1 as i32,
)
} else {
(0, 0)
};
event.data.mouse_move = Some(MouseMoveInfo {
from_position: from_pos,
to_position: to_pos,
delta,
is_dragging: false,
drag_button: None,
});
event
}
pub fn new_mouse_drag(
from_pos: (usize, usize),
to_pos: (usize, usize),
button: MouseButton,
zone_id: Option<String>,
) -> Self {
let mut event = Self::new(EventType::MouseMove, Some(to_pos), zone_id);
let delta = (
to_pos.0 as i32 - from_pos.0 as i32,
to_pos.1 as i32 - from_pos.1 as i32,
);
event.data.mouse_move = Some(MouseMoveInfo {
from_position: Some(from_pos),
to_position: to_pos,
delta,
is_dragging: true,
drag_button: Some(button),
});
event
}
pub fn new_hover_enter(
position: (usize, usize),
zone_id: String,
previous_zone: Option<String>,
) -> Self {
let mut event = Self::new(EventType::Hover, Some(position), Some(zone_id.clone()));
event.data.hover = Some(HoverInfo {
state: HoverState::Enter,
previous_zone,
current_zone: Some(zone_id),
hover_duration: None,
});
event
}
pub fn new_hover_leave(
position: (usize, usize),
zone_id: String,
new_zone: Option<String>,
) -> Self {
let mut event = Self::new(EventType::Hover, Some(position), Some(zone_id.clone()));
event.data.hover = Some(HoverInfo {
state: HoverState::Leave,
previous_zone: Some(zone_id),
current_zone: new_zone,
hover_duration: None,
});
event
}
pub fn new_hover_move(
position: (usize, usize),
zone_id: String,
duration: std::time::Duration,
) -> Self {
let mut event = Self::new(EventType::Hover, Some(position), Some(zone_id.clone()));
event.data.hover = Some(HoverInfo {
state: HoverState::Move,
previous_zone: None,
current_zone: Some(zone_id),
hover_duration: Some(duration),
});
event
}
pub fn new_box_resize(
resize_type: BoxResizeType,
original_bounds: (usize, usize, usize, usize),
new_bounds: (usize, usize, usize, usize),
anchor: ResizeAnchor,
state: ResizeState,
) -> Self {
let mut event = Self::new(EventType::BoxResize, None, None);
event.data.box_resize = Some(BoxResizeInfo {
resize_type,
original_bounds,
new_bounds,
anchor,
state,
});
event
}
pub fn new_title_change(
old_title: Option<String>,
new_title: String,
source: TitleChangeSource,
persist: bool,
) -> Self {
let mut event = Self::new(EventType::TitleChange, None, None);
event.data.title_change = Some(TitleChangeInfo {
old_title,
new_title,
source,
persist,
});
event
}
pub fn mouse_button(&self) -> Option<&MouseButton> {
self.data.mouse_button.as_ref()
}
pub fn key_info(&self) -> Option<&KeyInfo> {
self.data.key.as_ref()
}
pub fn scroll_info(&self) -> Option<&ScrollInfo> {
self.data.scroll.as_ref()
}
pub fn is_click(&self) -> bool {
self.event_type == EventType::Click
}
pub fn is_double_click(&self) -> bool {
self.event_type == EventType::DoubleClick
}
pub fn is_keyboard(&self) -> bool {
self.event_type == EventType::KeyPress
}
pub fn is_mouse_move(&self) -> bool {
self.event_type == EventType::MouseMove
}
pub fn is_box_resize(&self) -> bool {
self.event_type == EventType::BoxResize
}
pub fn is_title_change(&self) -> bool {
self.event_type == EventType::TitleChange
}
pub fn mouse_move_info(&self) -> Option<&MouseMoveInfo> {
self.data.mouse_move.as_ref()
}
pub fn hover_info(&self) -> Option<&HoverInfo> {
self.data.hover.as_ref()
}
pub fn box_resize_info(&self) -> Option<&BoxResizeInfo> {
self.data.box_resize.as_ref()
}
pub fn title_change_info(&self) -> Option<&TitleChangeInfo> {
self.data.title_change.as_ref()
}
pub fn is_drag(&self) -> bool {
if let Some(mouse_move) = &self.data.mouse_move {
mouse_move.is_dragging
} else {
false
}
}
pub fn is_hover_enter(&self) -> bool {
if let Some(hover) = &self.data.hover {
hover.state == HoverState::Enter
} else {
false
}
}
pub fn is_hover_leave(&self) -> bool {
if let Some(hover) = &self.data.hover {
hover.state == HoverState::Leave
} else {
false
}
}
pub fn movement_delta(&self) -> Option<(i32, i32)> {
self.data.mouse_move.as_ref().map(|m| m.delta)
}
}
pub trait RenderableContent {
fn get_dimensions(&self) -> (usize, usize);
fn get_raw_content(&self) -> String;
fn get_box_relative_sensitive_zones(&self) -> Vec<SensitiveZone>;
fn handle_event(&mut self, event: &ContentEvent) -> EventResult;
}
#[derive(Debug, Clone)]
pub struct SensitiveZone {
pub bounds: Bounds,
pub content_id: String,
pub content_type: ContentType,
pub metadata: SensitiveMetadata,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ContentType {
Choice,
Link,
Button,
ChartElement,
Tab,
Interactive,
}
#[derive(Debug, Clone, Default)]
pub struct SensitiveMetadata {
pub display_text: Option<String>,
pub tooltip: Option<String>,
pub selected: bool,
pub enabled: bool,
pub original_line: Option<usize>,
pub char_range: Option<(usize, usize)>,
}
impl SensitiveZone {
pub fn new(bounds: Bounds, content_id: String, content_type: ContentType) -> Self {
Self {
bounds,
content_id,
content_type,
metadata: SensitiveMetadata::default(),
}
}
pub fn with_metadata(
bounds: Bounds,
content_id: String,
content_type: ContentType,
metadata: SensitiveMetadata,
) -> Self {
Self {
bounds,
content_id,
content_type,
metadata,
}
}
pub fn contains(&self, x: usize, y: usize) -> bool {
self.bounds.contains_point(x, y)
}
pub fn display_text(&self) -> String {
self.metadata
.display_text
.clone()
.unwrap_or_else(|| self.content_id.clone())
}
}
#[derive(Debug, Clone)]
pub struct ContentDimensions {
pub width: usize,
pub height: usize,
pub needs_horizontal_scroll: bool,
pub needs_vertical_scroll: bool,
}
impl ContentDimensions {
pub fn new(width: usize, height: usize, viewport_width: usize, viewport_height: usize) -> Self {
Self {
width,
height,
needs_horizontal_scroll: width > viewport_width,
needs_vertical_scroll: height > viewport_height,
}
}
pub fn needs_scrolling(&self) -> bool {
self.needs_horizontal_scroll || self.needs_vertical_scroll
}
}