-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to inject custom events into application? #405
Comments
Yes, implementing a |
@hecrj the Trait |
finally I've managed with Tick message. A bit dirty, but... works for me: extern crate chrono;
extern crate iced;
extern crate midir;
use std::cell::RefCell;
use std::error::Error;
use std::io::{stdin, stdout, Write};
use std::option::Option::Some;
use std::sync::mpsc::{channel, Receiver, Sender};
use iced::{
button, executor, pick_list, scrollable, time, Align, Application, Button, Column, Command,
Container, Element, Length, PickList, Scrollable, Settings, Space, Subscription, Text,
};
use midir::{Ignore, MidiInput, MidiInputConnection, MidiInputPort, MidiInputPorts};
pub fn main() -> iced::Result {
BigNote::run(Settings::default())
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct Port {
pub index: usize,
pub name: Box<String>,
}
impl std::fmt::Display for Port {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// let midi_in = MidiInput::new("MyBigNote").unwrap();
write!(
f,
"{}",
// midi_in
// .port_name(midi_in.ports().get(self.index).unwrap())
// .unwrap()
&self.name
)
}
}
#[derive(Debug, Clone)]
enum Message {
PortSelected(Port),
Tick(chrono::DateTime<chrono::Local>),
}
struct BigNote {
note: i32,
need_init: bool,
client_name: String,
scroll: scrollable::State,
ports_list: pick_list::State<Port>,
selected_port: Option<Port>,
midi_in: MidiInput,
connection: Option<MidiInputConnection<Sender<Vec<u8>>>>,
reciever: Option<Receiver<Vec<u8>>>,
}
impl BigNote {
fn ports_names(&self) -> Vec<Port> {
let mut names: Vec<Port> = Vec::new();
let midi_in = &self.midi_in;
let ports_amount = midi_in.port_count();
for port in 0..ports_amount {
names.push(Port {
index: port,
name: Box::new(
midi_in
.port_name(midi_in.ports().get(port).unwrap())
.unwrap(),
),
});
}
names.into()
}
fn connect(&mut self) {
println!("init:");
let ports = &self.midi_in.ports();
let selected_port = self.selected_port.as_ref().unwrap();
let port = ports.get(selected_port.index).unwrap();
println!("in port name: {}", &self.midi_in.port_name(&port).unwrap());
let (sender, reciever) = channel();
self.reciever = Some(reciever);
let midi_in = MidiInput::new(&self.client_name).unwrap();
self.connection = Some(
midi_in
.connect(
&port,
"name",
|stamp, message, sender| {
// println!("{}: {:?} len = {}", stamp, message, message.len());
let mut packet = Vec::new();
packet.extend_from_slice(message);
sender.send(packet).unwrap();
},
sender,
)
.unwrap(),
);
}
}
impl Application for BigNote {
type Executor = executor::Default;
type Message = Message;
type Flags = ();
fn new(_flags: ()) -> (BigNote, Command<self::Message>) {
(
BigNote {
note: -1,
need_init: true,
client_name: String::from("MyBigNote"),
connection: Option::None,
ports_list: pick_list::State::<Port>::default(),
scroll: scrollable::State::default(),
selected_port: None,
midi_in: MidiInput::new("MyBigNote").unwrap(),
reciever: None,
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("MyBigNote by Levitanus")
}
fn view(&mut self) -> Element<Message> {
let port_names = self.ports_names();
let pick_list = PickList::new(
&mut self.ports_list,
port_names,
self.selected_port.clone(),
Message::PortSelected,
);
// let mut content = Scrollable::new(&mut self.scroll)
// .width(Length::Fill)
// .align_items(Align::Center)
// .spacing(10)
// .push(Space::with_height(Length::Units(600)))
// .push(Text::new("Which is your favorite String?"))
// .push(pick_list);
// content = content.push(Space::with_height(Length::Units(600)));
Container::new(pick_list)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::PortSelected(port) => {
println!("PortSelected");
self.selected_port = Some(port);
self.connect();
}
Message::Tick(_loc_time) => {
if self.reciever.is_some() {
let reciever = &self.reciever.as_ref().unwrap();
loop {
let recv_result = reciever.try_recv();
if !recv_result.is_err() {
println!("{:?}", recv_result.as_ref().unwrap());
} else {
break;
}
}
}
}
}
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
let fps = 30;
time::every(std::time::Duration::from_millis(1000 / fps))
.map(|_| Message::Tick(chrono::Local::now()))
}
} |
I have exactly the same problem, I need to transfer data from a static callback to the application via a subscription. The callback looks like this:
I had a look at the progress bar example, but I don't understand really how subscriptions work and how to implement a receipe for this. Is there a basic example that shows this use case without too much noise distracting from the real problem? |
@sourcebox, code I've place above does exactly what You want. Maybe with not clean way with By the advice of @Boddinagg I've used |
@Levitanus I saw your code, but I really like to do it in a clean way. |
Here's some progress on this: I managed to get it partially work, but not in a proper way. fn subscription(&self) -> Subscription<Self::Message> {
let tick_subscription =
time::every(std::time::Duration::from_millis(1000)).map(|_| Message::Tick);
let (sender, receiver) = mpsc::channel();
self.midi.set_midi_in_sender(&sender);
let midi_subscription =
Subscription::from_recipe(MidiReceiveSubscription { receiver: receiver }).map(|data| {
if data[0] == 0xB0 {
Message::MidiCCReceived(data[0] & 0x0F, data[1], data[2])
} else {
Message::MidiUnknownReceived
}
});
let subscriptions = vec![tick_subscription, midi_subscription];
Subscription::batch(subscriptions.into_iter())
}
impl<H, I> iced_native::subscription::Recipe<H, I> for MidiReceiveSubscription
where
H: Hasher,
{
type Output = Vec<u8>;
fn hash(&self, state: &mut H) {
struct Marker;
std::any::TypeId::of::<Marker>().hash(state);
}
fn stream(
self: Box<Self>,
_input: futures::stream::BoxStream<'static, I>,
) -> futures::stream::BoxStream<'static, Self::Output> {
Box::pin(futures::stream::unfold(
self.receiver,
move |state| async move {
let receiver = &state;
let result = receiver.recv();
if result.is_ok() {
Some((result.unwrap(), state))
} else {
None
}
},
))
}
} The problem is that the mpsc channel is instanced inside the subscription function every time a new message is received. To my understanding, this is not the way to do it. But my attempts to change that all failed. First subscription() takes &self as immutable, so there is no way to pass something in and out or set variables inside the application object. Second, the receipe does not take a reference to the mpsc receiver because it requires a static lifetime. When the MIDI subscription is active, I'm not able to quit the application via Cmd-Q anymore, this is also something I need to solve. @hecrj I would really appreciate any help on this topic. I think this whole thing is an easy task if you understand how it works, but I have to say, I still don't have a clue what's going on internally. |
Hello,
I have an app that needs to process MIDI events. They come from
midir
crate via a callback. The thing is, I don't quite understand, how to inject them into an application logic. Ideally I'd like them to be treated something likeMessage::MidiEvent(event)
along with usual button presses and mouse events.What's the best way to achieve that? If I'm not mistaken, it should be done by creating custom
Recipe
and subscribing to it. But maybe there is an easier way? In contrast to the download progress example, in my case it's just a plain event with no associated state. I just set up an input port and then receive callbacks on every MIDI event that I'd like to wrap into an application specific message.Thank you.
The text was updated successfully, but these errors were encountered: