use crate::node_id::NodeId;
use crate::validation::ValidationResult;
use crate::worker::{WorkerId, WorkerState};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandPaletteCommand {
pub id: String,
pub title: String,
pub help: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AsyncDirectoryEntry {
pub path: String,
pub label: String,
pub is_dir: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsyncTaskRequest {
ReadDirectory { path: String, show_hidden: bool },
Sleep { duration: Duration, label: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsyncTaskResult {
DirectoryEntries {
path: String,
entries: Vec<AsyncDirectoryEntry>,
},
SleepFinished {
label: String,
elapsed: Duration,
},
Failed {
path: String,
error: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClearRequested;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HeaderIconPressed;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HelpPanelFocusedHelpCleared;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CommandPaletteOpened;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CommandPaletteClosed;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SelectionListSelectedChanged;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ToastDismissed;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NavigatorUpdated;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TabsCleared;
#[derive(Debug, Clone)]
pub struct InputChanged {
pub value: String,
pub validation: ValidationResult,
}
#[derive(Debug, Clone)]
pub struct InputSubmitted {
pub value: String,
}
#[derive(Debug, Clone)]
pub struct InputBlurred {
pub value: String,
}
#[derive(Debug, Clone)]
pub struct TextAreaChanged {
pub value: String,
}
#[derive(Debug, Clone)]
pub struct TextAreaSelectionChanged {
pub start: (usize, usize),
pub end: (usize, usize),
}
#[derive(Debug, Clone)]
pub struct TextEditClipboardCopyRequested {
pub text: String,
pub cut: bool,
}
#[derive(Debug, Clone)]
pub struct TextEditClipboardPasteRequested {
pub target: NodeId,
}
#[derive(Debug, Clone)]
pub struct TextEditClipboardPaste {
pub target: NodeId,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct ButtonPressed {
pub description: String,
pub button_id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CheckboxChanged {
pub checked: bool,
}
#[derive(Debug, Clone)]
pub struct SwitchChanged {
pub value: bool,
}
#[derive(Debug, Clone)]
pub struct RadioButtonChanged {
pub value: bool,
}
#[derive(Debug, Clone)]
pub struct RadioSetChanged {
pub index: usize,
pub button_id: NodeId,
}
#[derive(Debug, Clone)]
pub struct ListViewSelectionChanged {
pub index: usize,
pub item: String,
}
#[derive(Debug, Clone)]
pub struct ListViewItemActivated {
pub index: usize,
pub item: String,
}
#[derive(Debug, Clone)]
pub struct OptionHighlighted {
pub index: usize,
}
#[derive(Debug, Clone)]
pub struct OptionSelected {
pub index: usize,
}
#[derive(Debug, Clone)]
pub struct SelectChanged {
pub index: usize,
pub label: String,
}
#[derive(Debug, Clone)]
pub struct SelectionListToggled {
pub index: usize,
pub selected: bool,
}
#[derive(Debug, Clone)]
pub struct TabActivated {
pub id: String,
pub index: usize,
pub title: String,
}
#[derive(Debug, Clone)]
pub struct TabClicked {
pub id: String,
pub title: String,
}
#[derive(Debug, Clone)]
pub struct TabDisabled {
pub id: String,
}
#[derive(Debug, Clone)]
pub struct TabEnabled {
pub id: String,
}
#[derive(Debug, Clone)]
pub struct TabHidden {
pub id: String,
}
#[derive(Debug, Clone)]
pub struct TabShown {
pub id: String,
}
#[derive(Debug, Clone)]
pub struct TabPaneFocused {
pub id: String,
}
#[derive(Debug, Clone)]
pub struct HeaderToggled {
pub tall: bool,
}
#[derive(Debug, Clone)]
pub struct FooterBindingsUpdated {
pub count: usize,
}
#[derive(Debug, Clone)]
pub struct ScreenTitleChanged {
pub title: Option<String>,
pub sub_title: Option<String>,
}
#[derive(Debug, Clone)]
pub struct HelpPanelSetHelp {
pub panel: NodeId,
pub markup: String,
}
#[derive(Debug, Clone)]
pub struct HelpPanelClearHelp {
pub panel: NodeId,
}
#[derive(Debug, Clone)]
pub struct HelpPanelFocusedHelpChanged {
pub source: NodeId,
pub markup: String,
}
#[derive(Debug, Clone)]
pub struct TreeNodeSelected {
pub index: usize,
pub label: String,
pub data: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TreeNodeActivated {
pub index: usize,
pub label: String,
pub data: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TreeNodeToggled {
pub index: usize,
pub label: String,
pub expanded: bool,
}
#[derive(Debug, Clone)]
pub struct TreeNodeCollapsed {
pub index: usize,
pub label: String,
}
#[derive(Debug, Clone)]
pub struct TreeNodeExpanded {
pub index: usize,
pub label: String,
}
#[derive(Debug, Clone)]
pub struct TreeNodeHighlighted {
pub index: usize,
pub label: String,
}
#[derive(Debug, Clone)]
pub struct MarkdownTableOfContentsSelected {
pub block_id: String,
}
#[derive(Debug, Clone)]
pub struct MarkdownTableOfContentsUpdated {
pub headings: Vec<(usize, String, String)>,
}
#[derive(Debug, Clone)]
pub struct DirectoryTreeFileSelected {
pub index: usize,
pub path: String,
}
#[derive(Debug, Clone)]
pub struct DirectoryTreeDirectorySelected {
pub index: usize,
pub path: String,
}
#[derive(Debug, Clone)]
pub struct OverlaySetVisible {
pub overlay: NodeId,
pub visible: bool,
}
#[derive(Debug, Clone)]
pub struct OverlaySetAnchor {
pub overlay: NodeId,
pub x: usize,
pub y: usize,
}
#[derive(Debug, Clone)]
pub struct OverlayClearAnchor {
pub overlay: NodeId,
}
#[derive(Debug, Clone)]
pub struct OverlayToggle {
pub overlay: NodeId,
}
#[derive(Debug, Clone)]
pub struct OverlayDismissRequested {
pub overlay: Option<NodeId>,
}
#[derive(Debug, Clone)]
pub struct OverlayVisibilityChanged {
pub overlay: NodeId,
pub visible: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppBack;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppBell;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppChangeTheme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppCommandPalette;
#[derive(Debug, Clone)]
pub struct AppFocus {
pub widget_id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppFocusNext;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppFocusPrevious;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppHelpQuit;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppCopySelectedText;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppHideHelpPanel;
#[derive(Debug, Clone)]
pub struct AppAddClass {
pub selector: String,
pub class_name: String,
}
#[derive(Debug, Clone)]
pub struct AppRemoveClass {
pub selector: String,
pub class_name: String,
}
#[derive(Debug, Clone)]
pub struct AppToggleClass {
pub selector: String,
pub class_name: String,
}
#[derive(Debug, Clone)]
pub struct AppSetDisabled {
pub selector: String,
pub disabled: bool,
}
#[derive(Debug, Clone)]
pub struct AppNotify {
pub message: String,
pub title: String,
pub severity: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppPopScreen;
#[derive(Debug, Clone)]
pub struct AppPushScreen {
pub screen: String,
}
#[derive(Debug, Clone)]
pub struct AppScreenshot {
pub filename: Option<String>,
pub path: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppShowHelpPanel;
#[derive(Debug, Clone)]
pub struct AppSimulateKey {
pub key: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppSuspendProcess;
#[derive(Debug, Clone)]
pub struct AppSwitchMode {
pub mode: String,
}
#[derive(Debug, Clone)]
pub struct AppSwitchScreen {
pub screen: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppToggleDark;
#[derive(Debug, Clone)]
pub struct ActionDispatchRequested {
pub action: String,
}
#[derive(Debug, Clone)]
pub struct CommandPaletteCommandSelected {
pub id: String,
pub title: String,
}
#[derive(Debug, Clone)]
pub struct CommandPaletteSetCommands {
pub commands: Vec<CommandPaletteCommand>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollbarAxis {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScrollbarScrollTo {
pub axis: ScrollbarAxis,
pub offset: f32,
pub animate: bool,
pub scroll_duration: Option<Duration>,
}
#[derive(Debug, Clone)]
pub struct DataTableCursorMoved {
pub row: usize,
pub column: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableHeaderSelected {
pub column: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableCellActivated {
pub row: usize,
pub column: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableCellHighlighted {
pub row: usize,
pub col: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableRowHighlighted {
pub row: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableRowSelected {
pub row: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableColumnHighlighted {
pub col: usize,
}
#[derive(Debug, Clone)]
pub struct DataTableColumnSelected {
pub col: usize,
}
#[derive(Debug, Clone)]
pub struct PlaceholderVariantChanged {
pub variant: String,
}
#[derive(Debug, Clone)]
pub struct CollapsibleToggled {
pub collapsed: bool,
}
#[derive(Debug, Clone)]
pub struct LinkClicked {
pub url: String,
}
#[derive(Debug, Clone)]
pub struct KeyPanelBindingsUpdated {
pub count: usize,
}
#[derive(Debug, Clone)]
pub struct KeyPanelScrolled {
pub offset: usize,
pub max_offset: usize,
}
#[derive(Debug, Clone)]
pub struct RichLogScrolled {
pub offset: usize,
pub max_offset: usize,
}
#[derive(Debug, Clone)]
pub struct AsyncTaskSpawn {
pub task_id: u64,
pub target: NodeId,
pub request: AsyncTaskRequest,
}
#[derive(Debug, Clone)]
pub struct AsyncTaskCancel {
pub task_id: u64,
}
#[derive(Debug, Clone)]
pub struct AsyncTaskCancelTarget {
pub target: NodeId,
}
#[derive(Debug, Clone)]
pub struct AsyncTaskCompleted {
pub task_id: u64,
pub target: NodeId,
pub result: AsyncTaskResult,
}
#[derive(Debug, Clone)]
pub struct AsyncTaskCancelled {
pub task_id: u64,
pub target: NodeId,
}
#[derive(Debug, Clone)]
pub struct TimerSchedule {
pub timer_id: u64,
pub target: NodeId,
pub delay: Duration,
}
#[derive(Debug, Clone)]
pub struct TimerCancel {
pub timer_id: u64,
}
#[derive(Debug, Clone)]
pub struct TimerFired {
pub timer_id: u64,
pub target: NodeId,
}
#[derive(Debug, Clone)]
pub struct TimerCancelled {
pub timer_id: u64,
pub target: NodeId,
}
#[derive(Debug, Clone)]
pub struct WorkerStateChanged {
pub worker_id: WorkerId,
pub state: WorkerState,
}
pub trait UserMessage: std::any::Any + Send + Sync + std::fmt::Debug + 'static {
fn as_any(&self) -> &dyn std::any::Any;
fn clone_box(&self) -> Box<dyn UserMessage>;
fn can_replace(&self, _pending: &dyn UserMessage) -> bool {
false
}
}
impl Clone for Box<dyn UserMessage> {
fn clone(&self) -> Self {
self.clone_box()
}
}
macro_rules! impl_message_from {
($($Variant:ident),* $(,)?) => {
$(
impl From<$Variant> for Message {
fn from(v: $Variant) -> Self {
Message::$Variant(v)
}
}
)*
};
}
#[derive(Debug, Clone)]
pub enum Message {
ClearRequested(ClearRequested),
HeaderIconPressed(HeaderIconPressed),
HelpPanelFocusedHelpCleared(HelpPanelFocusedHelpCleared),
CommandPaletteOpened(CommandPaletteOpened),
CommandPaletteClosed(CommandPaletteClosed),
SelectionListSelectedChanged(SelectionListSelectedChanged),
ToastDismissed(ToastDismissed),
NavigatorUpdated(NavigatorUpdated),
TabsCleared(TabsCleared),
InputChanged(InputChanged),
InputSubmitted(InputSubmitted),
InputBlurred(InputBlurred),
TextAreaChanged(TextAreaChanged),
TextAreaSelectionChanged(TextAreaSelectionChanged),
TextEditClipboardCopyRequested(TextEditClipboardCopyRequested),
TextEditClipboardPasteRequested(TextEditClipboardPasteRequested),
TextEditClipboardPaste(TextEditClipboardPaste),
ButtonPressed(ButtonPressed),
CheckboxChanged(CheckboxChanged),
SwitchChanged(SwitchChanged),
RadioButtonChanged(RadioButtonChanged),
RadioSetChanged(RadioSetChanged),
ListViewSelectionChanged(ListViewSelectionChanged),
ListViewItemActivated(ListViewItemActivated),
OptionHighlighted(OptionHighlighted),
OptionSelected(OptionSelected),
SelectChanged(SelectChanged),
SelectionListToggled(SelectionListToggled),
TabActivated(TabActivated),
TabClicked(TabClicked),
TabDisabled(TabDisabled),
TabEnabled(TabEnabled),
TabHidden(TabHidden),
TabShown(TabShown),
TabPaneFocused(TabPaneFocused),
HeaderToggled(HeaderToggled),
FooterBindingsUpdated(FooterBindingsUpdated),
ScreenTitleChanged(ScreenTitleChanged),
HelpPanelSetHelp(HelpPanelSetHelp),
HelpPanelClearHelp(HelpPanelClearHelp),
HelpPanelFocusedHelpChanged(HelpPanelFocusedHelpChanged),
TreeNodeSelected(TreeNodeSelected),
TreeNodeActivated(TreeNodeActivated),
TreeNodeToggled(TreeNodeToggled),
TreeNodeCollapsed(TreeNodeCollapsed),
TreeNodeExpanded(TreeNodeExpanded),
TreeNodeHighlighted(TreeNodeHighlighted),
DirectoryTreeFileSelected(DirectoryTreeFileSelected),
DirectoryTreeDirectorySelected(DirectoryTreeDirectorySelected),
MarkdownTableOfContentsSelected(MarkdownTableOfContentsSelected),
MarkdownTableOfContentsUpdated(MarkdownTableOfContentsUpdated),
OverlaySetVisible(OverlaySetVisible),
OverlaySetAnchor(OverlaySetAnchor),
OverlayClearAnchor(OverlayClearAnchor),
OverlayToggle(OverlayToggle),
OverlayDismissRequested(OverlayDismissRequested),
OverlayVisibilityChanged(OverlayVisibilityChanged),
AppBack(AppBack),
AppBell(AppBell),
AppChangeTheme(AppChangeTheme),
AppCommandPalette(AppCommandPalette),
AppFocus(AppFocus),
AppFocusNext(AppFocusNext),
AppFocusPrevious(AppFocusPrevious),
AppHelpQuit(AppHelpQuit),
AppCopySelectedText(AppCopySelectedText),
AppHideHelpPanel(AppHideHelpPanel),
AppAddClass(AppAddClass),
AppRemoveClass(AppRemoveClass),
AppToggleClass(AppToggleClass),
AppSetDisabled(AppSetDisabled),
AppNotify(AppNotify),
AppPopScreen(AppPopScreen),
AppPushScreen(AppPushScreen),
AppScreenshot(AppScreenshot),
AppShowHelpPanel(AppShowHelpPanel),
AppSimulateKey(AppSimulateKey),
AppSuspendProcess(AppSuspendProcess),
AppSwitchMode(AppSwitchMode),
AppSwitchScreen(AppSwitchScreen),
AppToggleDark(AppToggleDark),
ActionDispatchRequested(ActionDispatchRequested),
CommandPaletteCommandSelected(CommandPaletteCommandSelected),
CommandPaletteSetCommands(CommandPaletteSetCommands),
ScrollbarScrollTo(ScrollbarScrollTo),
DataTableCursorMoved(DataTableCursorMoved),
DataTableHeaderSelected(DataTableHeaderSelected),
DataTableCellActivated(DataTableCellActivated),
DataTableCellHighlighted(DataTableCellHighlighted),
DataTableRowHighlighted(DataTableRowHighlighted),
DataTableRowSelected(DataTableRowSelected),
DataTableColumnHighlighted(DataTableColumnHighlighted),
DataTableColumnSelected(DataTableColumnSelected),
PlaceholderVariantChanged(PlaceholderVariantChanged),
CollapsibleToggled(CollapsibleToggled),
LinkClicked(LinkClicked),
KeyPanelBindingsUpdated(KeyPanelBindingsUpdated),
KeyPanelScrolled(KeyPanelScrolled),
RichLogScrolled(RichLogScrolled),
AsyncTaskSpawn(AsyncTaskSpawn),
AsyncTaskCancel(AsyncTaskCancel),
AsyncTaskCancelTarget(AsyncTaskCancelTarget),
AsyncTaskCompleted(AsyncTaskCompleted),
AsyncTaskCancelled(AsyncTaskCancelled),
TimerSchedule(TimerSchedule),
TimerCancel(TimerCancel),
TimerFired(TimerFired),
TimerCancelled(TimerCancelled),
WorkerStateChanged(WorkerStateChanged),
Custom(Box<dyn UserMessage>),
}
impl_message_from!(
ClearRequested,
HeaderIconPressed,
HelpPanelFocusedHelpCleared,
CommandPaletteOpened,
CommandPaletteClosed,
SelectionListSelectedChanged,
ToastDismissed,
TabsCleared,
InputChanged,
InputSubmitted,
InputBlurred,
TextAreaChanged,
TextAreaSelectionChanged,
TextEditClipboardCopyRequested,
TextEditClipboardPasteRequested,
TextEditClipboardPaste,
ButtonPressed,
CheckboxChanged,
SwitchChanged,
RadioButtonChanged,
RadioSetChanged,
ListViewSelectionChanged,
ListViewItemActivated,
OptionHighlighted,
OptionSelected,
SelectChanged,
SelectionListToggled,
TabActivated,
TabClicked,
TabDisabled,
TabEnabled,
TabHidden,
TabShown,
TabPaneFocused,
HeaderToggled,
FooterBindingsUpdated,
ScreenTitleChanged,
HelpPanelSetHelp,
HelpPanelClearHelp,
HelpPanelFocusedHelpChanged,
TreeNodeSelected,
TreeNodeActivated,
TreeNodeToggled,
TreeNodeCollapsed,
TreeNodeExpanded,
TreeNodeHighlighted,
DirectoryTreeFileSelected,
DirectoryTreeDirectorySelected,
MarkdownTableOfContentsSelected,
MarkdownTableOfContentsUpdated,
OverlaySetVisible,
OverlaySetAnchor,
OverlayClearAnchor,
OverlayToggle,
OverlayDismissRequested,
OverlayVisibilityChanged,
AppBack,
AppBell,
AppChangeTheme,
AppCommandPalette,
AppFocus,
AppFocusNext,
AppFocusPrevious,
AppHelpQuit,
AppCopySelectedText,
AppHideHelpPanel,
AppAddClass,
AppRemoveClass,
AppToggleClass,
AppSetDisabled,
AppNotify,
AppPopScreen,
AppPushScreen,
AppScreenshot,
AppShowHelpPanel,
AppSimulateKey,
AppSuspendProcess,
AppSwitchMode,
AppSwitchScreen,
AppToggleDark,
ActionDispatchRequested,
CommandPaletteCommandSelected,
CommandPaletteSetCommands,
ScrollbarScrollTo,
DataTableCursorMoved,
DataTableHeaderSelected,
DataTableCellActivated,
DataTableCellHighlighted,
DataTableRowHighlighted,
DataTableRowSelected,
DataTableColumnHighlighted,
DataTableColumnSelected,
PlaceholderVariantChanged,
CollapsibleToggled,
LinkClicked,
KeyPanelBindingsUpdated,
KeyPanelScrolled,
RichLogScrolled,
AsyncTaskSpawn,
AsyncTaskCancel,
AsyncTaskCancelTarget,
AsyncTaskCompleted,
AsyncTaskCancelled,
TimerSchedule,
TimerCancel,
TimerFired,
TimerCancelled,
WorkerStateChanged,
);
impl Message {
pub fn can_replace(&self, pending: &Message) -> bool {
use Message::*;
match (self, pending) {
(InputChanged(_), InputChanged(_))
| (TextAreaChanged(_), TextAreaChanged(_))
| (TextAreaSelectionChanged(_), TextAreaSelectionChanged(_))
| (DataTableCursorMoved(_), DataTableCursorMoved(_))
| (DataTableCellHighlighted(_), DataTableCellHighlighted(_))
| (DataTableRowHighlighted(_), DataTableRowHighlighted(_))
| (DataTableColumnHighlighted(_), DataTableColumnHighlighted(_))
| (TreeNodeHighlighted(_), TreeNodeHighlighted(_))
| (OptionHighlighted(_), OptionHighlighted(_))
| (KeyPanelScrolled(_), KeyPanelScrolled(_))
| (RichLogScrolled(_), RichLogScrolled(_)) => true,
(Custom(current), Custom(older)) => current.can_replace(older.as_ref()),
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct MessageEvent {
pub sender: NodeId,
pub message: Message,
pub control: Option<NodeId>,
}
#[derive(Debug, Clone)]
pub struct MessageEnvelope {
pub event: MessageEvent,
control: Option<NodeId>,
stopped: bool,
prevented: bool,
replaceable: bool,
}
impl MessageEnvelope {
pub fn new(event: MessageEvent) -> Self {
let control = event.control.or(Some(event.sender));
Self {
event,
control,
stopped: false,
prevented: false,
replaceable: false,
}
}
pub fn stop(&mut self) {
self.stopped = true;
}
pub fn is_stopped(&self) -> bool {
self.stopped
}
pub fn prevent_default(&mut self) {
self.prevented = true;
}
pub fn is_default_prevented(&self) -> bool {
self.prevented
}
pub fn set_replaceable(&mut self, replaceable: bool) {
self.replaceable = replaceable;
}
pub fn can_replace(&self) -> bool {
self.replaceable
}
pub fn control(&self) -> Option<NodeId> {
self.control
}
pub fn set_control(&mut self, node: NodeId) {
self.control = Some(node);
}
pub fn sender(&self) -> NodeId {
self.event.sender
}
pub fn message(&self) -> &Message {
&self.event.message
}
}
impl From<MessageEvent> for MessageEnvelope {
fn from(event: MessageEvent) -> Self {
Self::new(event)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node_id::node_id_from_ffi;
fn test_event() -> MessageEvent {
MessageEvent {
sender: node_id_from_ffi(1),
message: Message::ButtonPressed(ButtonPressed {
description: "ok".into(),
button_id: None,
}),
control: None,
}
}
#[test]
fn new_envelope_defaults_stopped_false() {
let env = MessageEnvelope::new(test_event());
assert!(!env.is_stopped());
}
#[test]
fn new_envelope_defaults_prevented_false() {
let env = MessageEnvelope::new(test_event());
assert!(!env.is_default_prevented());
}
#[test]
fn new_envelope_defaults_replaceable_false() {
let env = MessageEnvelope::new(test_event());
assert!(!env.can_replace());
}
#[test]
fn stop_sets_stopped_flag() {
let mut env = MessageEnvelope::new(test_event());
env.stop();
assert!(env.is_stopped());
}
#[test]
fn stop_is_idempotent() {
let mut env = MessageEnvelope::new(test_event());
env.stop();
env.stop();
assert!(env.is_stopped());
}
#[test]
fn prevent_default_sets_prevented_flag() {
let mut env = MessageEnvelope::new(test_event());
env.prevent_default();
assert!(env.is_default_prevented());
}
#[test]
fn prevent_default_is_idempotent() {
let mut env = MessageEnvelope::new(test_event());
env.prevent_default();
env.prevent_default();
assert!(env.is_default_prevented());
}
#[test]
fn set_replaceable_true() {
let mut env = MessageEnvelope::new(test_event());
env.set_replaceable(true);
assert!(env.can_replace());
}
#[test]
fn set_replaceable_false_after_true() {
let mut env = MessageEnvelope::new(test_event());
env.set_replaceable(true);
env.set_replaceable(false);
assert!(!env.can_replace());
}
#[test]
fn sender_returns_event_sender() {
let evt = test_event();
let expected_sender = evt.sender;
let env = MessageEnvelope::new(evt);
assert_eq!(env.sender(), expected_sender);
}
#[test]
fn message_returns_event_message() {
let env = MessageEnvelope::new(test_event());
match env.message() {
Message::ButtonPressed(ButtonPressed { description, .. }) => {
assert_eq!(description, "ok");
}
other => panic!("unexpected message variant: {:?}", other),
}
}
#[test]
fn from_message_event_defaults_all_flags_false() {
let env: MessageEnvelope = test_event().into();
assert!(!env.is_stopped());
assert!(!env.is_default_prevented());
assert!(!env.can_replace());
}
#[test]
fn from_preserves_sender_and_message() {
let evt = test_event();
let expected_sender = evt.sender;
let env: MessageEnvelope = evt.into();
assert_eq!(env.sender(), expected_sender);
assert!(matches!(env.message(), Message::ButtonPressed(..)));
}
#[test]
fn stop_does_not_affect_prevented() {
let mut env = MessageEnvelope::new(test_event());
env.stop();
assert!(!env.is_default_prevented());
}
#[test]
fn prevent_default_does_not_affect_stopped() {
let mut env = MessageEnvelope::new(test_event());
env.prevent_default();
assert!(!env.is_stopped());
}
#[test]
fn all_flags_independent() {
let mut env = MessageEnvelope::new(test_event());
env.stop();
env.prevent_default();
env.set_replaceable(true);
assert!(env.is_stopped());
assert!(env.is_default_prevented());
assert!(env.can_replace());
}
#[test]
fn clone_preserves_flags() {
let mut env = MessageEnvelope::new(test_event());
env.stop();
env.set_replaceable(true);
let cloned = env.clone();
assert!(cloned.is_stopped());
assert!(!cloned.is_default_prevented());
assert!(cloned.can_replace());
assert_eq!(cloned.control(), env.control());
}
#[test]
fn control_defaults_to_sender() {
let evt = test_event();
let expected = evt.sender;
let env = MessageEnvelope::new(evt);
assert_eq!(env.control(), Some(expected));
}
#[test]
fn from_message_event_sets_control_to_sender() {
let evt = test_event();
let expected = evt.sender;
let env: MessageEnvelope = evt.into();
assert_eq!(env.control(), Some(expected));
}
#[test]
fn set_control_overrides_default() {
let mut env = MessageEnvelope::new(test_event());
let other = node_id_from_ffi(42);
env.set_control(other);
assert_eq!(env.control(), Some(other));
assert_ne!(env.sender(), other);
}
#[test]
fn clone_preserves_control() {
let mut env = MessageEnvelope::new(test_event());
let other = node_id_from_ffi(99);
env.set_control(other);
let cloned = env.clone();
assert_eq!(cloned.control(), Some(other));
}
#[test]
fn from_unit_struct_into_message() {
let msg: Message = ClearRequested.into();
assert!(matches!(msg, Message::ClearRequested(..)));
}
#[test]
fn from_field_struct_into_message() {
let msg: Message = ButtonPressed {
description: "test".into(),
button_id: None,
}
.into();
assert!(matches!(msg, Message::ButtonPressed(..)));
}
#[test]
fn event_control_none_promoted_to_sender_by_envelope() {
let evt = test_event();
assert!(evt.control.is_none());
let env = MessageEnvelope::new(evt);
assert_eq!(env.control(), Some(node_id_from_ffi(1)));
}
#[test]
fn explicit_control_preserved_by_envelope() {
let other = node_id_from_ffi(42);
let evt = MessageEvent {
sender: node_id_from_ffi(1),
message: Message::ButtonPressed(ButtonPressed {
description: "ctrl".into(),
button_id: None,
}),
control: Some(other),
};
let env = MessageEnvelope::new(evt);
assert_eq!(env.control(), Some(other));
}
#[test]
fn control_none_is_allowed() {
let evt = MessageEvent {
sender: node_id_from_ffi(1),
message: Message::ClearRequested(ClearRequested),
control: None,
};
assert!(evt.control.is_none());
let env = MessageEnvelope::new(evt);
assert_eq!(env.control(), Some(node_id_from_ffi(1)));
}
#[test]
fn message_can_replace_known_variants() {
let a = Message::InputChanged(InputChanged {
value: "a".into(),
validation: ValidationResult::success(),
});
let b = Message::InputChanged(InputChanged {
value: "ab".into(),
validation: ValidationResult::success(),
});
assert!(b.can_replace(&a));
assert!(
!Message::ButtonPressed(ButtonPressed {
description: "x".into(),
button_id: None,
})
.can_replace(&Message::ButtonPressed(ButtonPressed {
description: "y".into(),
button_id: None,
}))
);
}
#[derive(Debug, Clone)]
struct ReplaceableCustom {
key: u8,
}
impl UserMessage for ReplaceableCustom {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn clone_box(&self) -> Box<dyn UserMessage> {
Box::new(self.clone())
}
fn can_replace(&self, pending: &dyn UserMessage) -> bool {
pending
.as_any()
.downcast_ref::<ReplaceableCustom>()
.map(|older| older.key == self.key)
.unwrap_or(false)
}
}
#[test]
fn custom_message_can_replace_uses_user_hook() {
let older = Message::Custom(Box::new(ReplaceableCustom { key: 7 }));
let newer_same_key = Message::Custom(Box::new(ReplaceableCustom { key: 7 }));
let newer_other_key = Message::Custom(Box::new(ReplaceableCustom { key: 9 }));
assert!(newer_same_key.can_replace(&older));
assert!(!newer_other_key.can_replace(&older));
}
}