use std::borrow::Cow;
use nom::number::streaming::be_u24;
use nom_derive::Nom;
use typed_builder::TypedBuilder;
use crate::PrintSentence;
#[cfg(feature = "async")]
use asynchronous::ExtcapControlSenderTrait as _;
#[cfg(feature = "sync")]
use synchronous::ExtcapControlSenderTrait as _;
#[cfg(feature = "async")]
pub mod asynchronous;
#[cfg(feature = "sync")]
pub mod synchronous;
pub trait EnableableControl: ToolbarControl {
fn set_enabled(&self, enabled: bool) -> ControlPacket<'static> {
ControlPacket::new_with_payload(
self.control_number(),
if enabled {
ControlCommand::Enable
} else {
ControlCommand::Disable
},
&[][..],
)
}
}
pub trait ControlWithLabel: ToolbarControl {
fn set_label<'a>(&self, label: &'a str) -> ControlPacket<'a> {
ControlPacket::new_with_payload(
self.control_number(),
ControlCommand::Set,
label.as_bytes(),
)
}
}
#[derive(Debug, TypedBuilder)]
pub struct BooleanControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(default, setter(strip_option, into))]
pub tooltip: Option<String>,
#[builder(default = false)]
pub default_value: bool,
}
impl EnableableControl for BooleanControl {}
impl ControlWithLabel for BooleanControl {}
impl BooleanControl {
pub fn set_checked<'a>(&self, checked: bool) -> ControlPacket<'a> {
ControlPacket::new_with_payload(
self.control_number(),
ControlCommand::Set,
vec![checked as u8],
)
}
}
impl PrintSentence for BooleanControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "control {{number={}}}", self.control_number())?;
write!(f, "{{type=boolean}}")?;
write!(f, "{{display={}}}", self.display)?;
write!(f, "{{default={}}}", self.default_value)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={}}}", tooltip)?;
}
writeln!(f)
}
}
impl ToolbarControl for BooleanControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
#[derive(Debug, TypedBuilder)]
pub struct ButtonControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(default, setter(strip_option, into))]
pub tooltip: Option<String>,
}
impl EnableableControl for ButtonControl {}
impl ControlWithLabel for ButtonControl {}
impl ToolbarControl for ButtonControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
impl PrintSentence for ButtonControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "control {{number={}}}", self.control_number())?;
write!(f, "{{type=button}}")?;
write!(f, "{{display={}}}", self.display)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={}}}", tooltip)?;
}
writeln!(f)
}
}
#[derive(Debug, TypedBuilder)]
pub struct LoggerControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(default, setter(strip_option, into))]
pub tooltip: Option<String>,
}
impl LoggerControl {
pub fn clear_and_add_log<'a>(&self, log: Cow<'a, str>) -> ControlPacket<'a> {
ControlPacket::new_with_payload(
self.control_number(),
ControlCommand::Set,
format!("{}\n", log).into_bytes(),
)
}
pub fn add_log<'a>(&self, log: Cow<'a, str>) -> ControlPacket<'a> {
ControlPacket::new_with_payload(
self.control_number(),
ControlCommand::Add,
format!("{}\n", log).into_bytes(),
)
}
}
impl ToolbarControl for LoggerControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
impl PrintSentence for LoggerControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "control {{number={}}}", self.control_number())?;
write!(f, "{{type=button}}")?;
write!(f, "{{role=logger}}")?;
write!(f, "{{display={}}}", self.display)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={tooltip}}}")?;
}
writeln!(f)
}
}
#[derive(Debug, TypedBuilder)]
pub struct HelpButtonControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(default, setter(strip_option, into))]
pub tooltip: Option<String>,
}
impl ToolbarControl for HelpButtonControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
impl PrintSentence for HelpButtonControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "control {{number={}}}", self.control_number())?;
write!(f, "{{type=button}}")?;
write!(f, "{{role=help}}")?;
write!(f, "{{display={}}}", self.display)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={tooltip}}}")?;
}
writeln!(f)
}
}
#[derive(Debug, TypedBuilder)]
pub struct RestoreButtonControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(default, setter(strip_option, into))]
pub tooltip: Option<String>,
}
impl ToolbarControl for RestoreButtonControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
impl PrintSentence for RestoreButtonControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "control {{number={}}}", self.control_number())?;
write!(f, "{{type=button}}")?;
write!(f, "{{role=restore}}")?;
write!(f, "{{display={}}}", self.display)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={tooltip}}}")?;
}
writeln!(f)
}
}
#[derive(Debug, TypedBuilder)]
pub struct SelectorControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(default, setter(strip_option, into))]
pub tooltip: Option<String>,
#[builder(default, setter(into))]
pub options: Vec<SelectorControlOption>,
}
impl SelectorControl {
pub fn set_value<'a>(&self, value: &'a str) -> ControlPacket<'a> {
ControlPacket::new_with_payload(
self.control_number(),
ControlCommand::Set,
value.as_bytes(),
)
}
pub fn add_value<'a>(&self, value: &'a str, display: Option<&'a str>) -> ControlPacket<'a> {
let payload_bytes: Cow<'a, [u8]> = match display {
Some(d) => Cow::Owned(format!("{}\0{}", value, d).as_bytes().to_vec()),
None => Cow::Borrowed(value.as_bytes()),
};
ControlPacket::new_with_payload(self.control_number(), ControlCommand::Add, payload_bytes)
}
pub fn remove_value<'a>(&self, value: &'a str) -> ControlPacket<'a> {
assert!(
!value.is_empty(),
"Argument to remove_value must not be empty"
);
ControlPacket::new_with_payload(
self.control_number(),
ControlCommand::Remove,
value.as_bytes(),
)
}
pub fn clear(&self) -> ControlPacket<'static> {
ControlPacket::new_with_payload(self.control_number(), ControlCommand::Remove, &[][..])
}
}
impl ToolbarControl for SelectorControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
impl PrintSentence for SelectorControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"control {{number={}}}{{type=selector}}",
self.control_number()
)?;
write!(f, "{{display={}}}", self.display)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={}}}", tooltip)?;
}
writeln!(f)?;
for value in self.options.iter() {
value.format_sentence(f, self)?;
}
Ok(())
}
}
#[derive(Clone, Debug, TypedBuilder)]
pub struct SelectorControlOption {
#[builder(setter(into))]
pub value: String,
#[builder(setter(into))]
pub display: String,
#[builder(default)]
pub default: bool,
}
impl SelectorControlOption {
pub fn format_sentence<C: ToolbarControl>(
&self,
f: &mut std::fmt::Formatter<'_>,
control: &C,
) -> std::fmt::Result {
write!(
f,
"value {{control={}}}{{value={}}}{{display={}}}",
control.control_number(),
self.value,
self.display,
)?;
if self.default {
write!(f, "{{default=true}}")?;
}
writeln!(f)?;
Ok(())
}
}
#[derive(Debug, Default, TypedBuilder)]
pub struct StringControl {
pub control_number: u8,
#[builder(setter(into))]
pub display: String,
#[builder(setter(into, strip_option))]
pub tooltip: Option<String>,
#[builder(setter(into, strip_option))]
pub placeholder: Option<String>,
#[builder(setter(into, strip_option))]
pub validation: Option<String>,
#[builder(default, setter(into, strip_option))]
pub default_value: Option<String>,
}
impl StringControl {
pub fn set_value<'a>(&self, message: &'a str) -> ControlPacket<'a> {
assert!(
message.as_bytes().len() <= 32767,
"message must not be longer than 32767 bytes"
);
ControlPacket::new_with_payload(
self.control_number,
ControlCommand::Set,
message.as_bytes(),
)
}
}
impl ToolbarControl for StringControl {
fn control_number(&self) -> u8 {
self.control_number
}
}
impl PrintSentence for StringControl {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"control {{number={}}}{{type=string}}",
self.control_number()
)?;
write!(f, "{{display={}}}", self.display)?;
if let Some(tooltip) = &self.tooltip {
write!(f, "{{tooltip={}}}", tooltip)?;
}
if let Some(placeholder) = &self.placeholder {
write!(f, "{{placeholder={}}}", placeholder)?;
}
if let Some(validation) = &self.validation {
write!(f, "{{validation={}}}", validation)?;
}
if let Some(default_value) = &self.default_value {
write!(f, "{{default={}}}", default_value)?;
}
writeln!(f)
}
}
pub trait ToolbarControl: PrintSentence {
fn control_number(&self) -> u8;
}
#[derive(Debug, Nom, Clone, PartialEq, Eq)]
pub struct ControlPacket<'a> {
#[nom(Verify = "*sync_pipe_indication == b'T'")]
pub sync_pipe_indication: u8,
#[nom(Parse = "be_u24")]
pub message_length: u32,
pub control_number: u8,
pub command: ControlCommand,
#[nom(Map = "Cow::from", Take = "(message_length - 2) as usize")]
pub payload: Cow<'a, [u8]>,
}
impl<'a> ControlPacket<'a> {
#[must_use]
pub fn new_with_payload<CowSlice: Into<Cow<'a, [u8]>>>(
control_number: u8,
command: ControlCommand,
payload: CowSlice,
) -> Self {
let payload = payload.into();
ControlPacket {
sync_pipe_indication: b'T',
message_length: (payload.len() + 2) as u32,
control_number,
command,
payload,
}
}
#[must_use]
pub fn new(control_number: u8, command: ControlCommand) -> Self {
let empty_slice: &'static [u8] = &[];
Self::new_with_payload(control_number, command, empty_slice)
}
pub fn to_header_bytes(&self) -> [u8; 6] {
let mut bytes = [0_u8; 6];
bytes[0] = self.sync_pipe_indication;
bytes[1..4].copy_from_slice(&self.message_length.to_be_bytes()[1..]);
bytes[4] = self.control_number;
bytes[5] = self.command as u8;
bytes
}
pub fn into_owned(self) -> ControlPacket<'static> {
ControlPacket {
payload: match self.payload {
Cow::Borrowed(v) => Cow::Owned(v.to_vec()),
Cow::Owned(v) => Cow::Owned(v),
},
..self
}
}
#[cfg(feature = "sync")]
pub fn send(self, sender: &mut synchronous::ExtcapControlSender) -> std::io::Result<()> {
sender.send(self)
}
#[cfg(feature = "async")]
pub async fn send_async(
self,
sender: &mut asynchronous::ExtcapControlSender,
) -> tokio::io::Result<()> {
sender.send(self).await
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Nom)]
#[repr(u8)]
pub enum ControlCommand {
Initialized = 0,
Set = 1,
Add = 2,
Remove = 3,
Enable = 4,
Disable = 5,
StatusbarMessage = 6,
InformationMessage = 7,
WarningMessage = 8,
ErrorMessage = 9,
}
#[cfg(test)]
mod test {
use nom_derive::Parse;
use super::ControlPacket;
#[test]
fn test_to_bytes() {
let packet = ControlPacket::new_with_payload(
123,
super::ControlCommand::InformationMessage,
&b"testing123"[..],
);
let full_bytes = [&packet.to_header_bytes(), packet.payload.as_ref()].concat();
let (rem, parsed_packet) = ControlPacket::parse(&full_bytes).unwrap();
assert_eq!(packet, parsed_packet);
assert!(rem.is_empty());
}
}