pub struct PortHandle<In, Out> {
pub rx: ChannelHandle<In>,
/* private fields */
}Expand description
A bidirectional port handle: inbound In + outbound Sender<Out>.
Use port.rx for inbound messages and port.tx() for the outbound sender.
§Example
ⓘ
let midi = port!(cx, MidiIn, MidiOut);
// Send the port's senders to an external connection
let tx_in = midi.rx.tx(); // external thread sends MidiIn here
let tx_out = midi.tx(); // component sends MidiOut here
effect_once!(cx, move || {
let conn = start_midi(tx_in, tx_out);
move || drop(conn)
});
// Read inbound messages
for msg in midi.rx.get() { /* ... */ }Fields§
§rx: ChannelHandle<In>Inbound channel (external -> component).
Implementations§
Source§impl<In: 'static, Out: 'static> PortHandle<In, Out>
impl<In: 'static, Out: 'static> PortHandle<In, Out>
Sourcepub fn new(wake: Arc<AtomicBool>) -> Self
pub fn new(wake: Arc<AtomicBool>) -> Self
Create a new bidirectional port with an event-loop wake flag.
Sourcepub fn tx(&self) -> Sender<Out>
pub fn tx(&self) -> Sender<Out>
Get a cloneable Sender<Out> for sending outbound messages.
Examples found in repository?
examples/39_port.rs (line 91)
36 fn render(&self, cx: Scope) -> View {
37 let show_help = state!(cx, || false);
38
39 cx.use_command(
40 KeyBinding::key(KeyCode::F(1)),
41 with!(show_help => move || show_help.update(|v| *v = !*v)),
42 );
43
44 let status = state!(cx, || "Idle".to_string());
45 let progress = state!(cx, || 0u8);
46 let result: State<Option<String>> = state!(cx, || None);
47 let running = state!(cx, || false);
48
49 // Bidirectional port: inbound TaskProgress, outbound TaskCommand
50 let port = port!(cx, TaskProgress, TaskCommand);
51
52 // Process incoming progress messages
53 for msg in port.rx.get() {
54 match msg {
55 TaskProgress::Started => {
56 status.set("Working...".to_string());
57 progress.set(0);
58 result.set(None);
59 running.set(true);
60 }
61 TaskProgress::Progress(pct) => {
62 status.set(format!("Progress: {}%", pct));
63 progress.set(pct);
64 }
65 TaskProgress::Done(data) => {
66 status.set("Done!".to_string());
67 progress.set(100);
68 result.set(Some(data));
69 running.set(false);
70 }
71 TaskProgress::Cancelled => {
72 status.set("Cancelled".to_string());
73 running.set(false);
74 }
75 }
76 }
77
78 // Spawn the worker thread on first render
79 let worker_started = state!(cx, || false);
80 if !worker_started.get() {
81 worker_started.set(true);
82 let tx_progress = port.rx.tx();
83 if let Some(rx_commands) = port.take_outbound_rx() {
84 std::thread::spawn(move || {
85 worker_loop(tx_progress, rx_commands);
86 });
87 }
88 }
89
90 let start_task = {
91 let tx = port.tx();
92 with!(running => move || {
93 if !running.get() {
94 let _ = tx.send(TaskCommand::Start);
95 }
96 })
97 };
98
99 let cancel_task = {
100 let tx = port.tx();
101 with!(running => move || {
102 if running.get() {
103 let _ = tx.send(TaskCommand::Cancel);
104 }
105 })
106 };
107
108 let pct = progress.get();
109 let bar_width = 30usize;
110 let filled = (pct as usize * bar_width) / 100;
111 let bar = format!(
112 "[{}{}] {}%",
113 "█".repeat(filled),
114 "░".repeat(bar_width - filled),
115 pct
116 );
117
118 View::vstack()
119 .spacing(1)
120 .child(View::styled_text("Port: Background Task Runner").bold().build())
121 .child(
122 View::hstack()
123 .spacing(1)
124 .child(View::styled_text("Status:").dim().build())
125 .child(View::styled_text(status.get())
126 .color(if running.get() { Color::Yellow } else if pct == 100 { Color::Green } else { Color::Reset })
127 .bold()
128 .build())
129 .build(),
130 )
131 .child(View::styled_text(&bar).color(
132 if pct == 100 { Color::Green }
133 else if pct > 50 { Color::Yellow }
134 else { Color::Cyan }
135 ).build())
136 .child(if let Some(data) = result.get() {
137 View::vstack()
138 .child(View::styled_text("Result:").dim().build())
139 .child(View::styled_text(format!(" {}", data)).color(Color::Green).build())
140 .build()
141 } else {
142 View::empty()
143 })
144 .child(
145 View::hstack()
146 .spacing(1)
147 .child(
148 View::button()
149 .label(if running.get() { "[ Running... ]" } else { "[ Start Task ]" })
150 .on_press(start_task)
151 .build(),
152 )
153 .child(
154 View::button()
155 .label("[ Cancel ]")
156 .on_press(cancel_task)
157 .build(),
158 )
159 .build(),
160 )
161 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
162 .child(
163 View::modal()
164 .visible(show_help.get())
165 .title("Example 39: Port")
166 .on_dismiss(with!(show_help => move || show_help.set(false)))
167 .child(
168 View::vstack()
169 .child(View::styled_text("What you're seeing").bold().build())
170 .child(View::text("• Background task with progress"))
171 .child(View::text("• Bidirectional communication"))
172 .child(View::text("• Start and cancel controls"))
173 .child(View::gap(1))
174 .child(View::styled_text("Key concepts").bold().build())
175 .child(View::text("• port!(cx, InType, OutType)"))
176 .child(View::text("• port.rx.tx() sends to UI"))
177 .child(View::text("• port.tx() sends to worker"))
178 .child(View::text("• port.take_outbound_rx() for worker"))
179 .child(View::text("• port.rx.get() reads this frame"))
180 .child(View::gap(1))
181 .child(View::styled_text("Try this").bold().build())
182 .child(View::text("• Start a task, watch progress"))
183 .child(View::text("• Cancel mid-way"))
184 .child(View::text("• Start another after completion"))
185 .child(View::gap(1))
186 .child(View::styled_text("Press Escape to close").dim().build())
187 .build(),
188 )
189 .build(),
190 )
191 .build()
192 }Sourcepub fn take_outbound_rx(&self) -> Option<Receiver<Out>>
pub fn take_outbound_rx(&self) -> Option<Receiver<Out>>
Take the outbound receiver (can only be called once).
This is intended for passing to an external thread that consumes
outbound messages. Returns None if already taken.
Examples found in repository?
examples/39_port.rs (line 83)
36 fn render(&self, cx: Scope) -> View {
37 let show_help = state!(cx, || false);
38
39 cx.use_command(
40 KeyBinding::key(KeyCode::F(1)),
41 with!(show_help => move || show_help.update(|v| *v = !*v)),
42 );
43
44 let status = state!(cx, || "Idle".to_string());
45 let progress = state!(cx, || 0u8);
46 let result: State<Option<String>> = state!(cx, || None);
47 let running = state!(cx, || false);
48
49 // Bidirectional port: inbound TaskProgress, outbound TaskCommand
50 let port = port!(cx, TaskProgress, TaskCommand);
51
52 // Process incoming progress messages
53 for msg in port.rx.get() {
54 match msg {
55 TaskProgress::Started => {
56 status.set("Working...".to_string());
57 progress.set(0);
58 result.set(None);
59 running.set(true);
60 }
61 TaskProgress::Progress(pct) => {
62 status.set(format!("Progress: {}%", pct));
63 progress.set(pct);
64 }
65 TaskProgress::Done(data) => {
66 status.set("Done!".to_string());
67 progress.set(100);
68 result.set(Some(data));
69 running.set(false);
70 }
71 TaskProgress::Cancelled => {
72 status.set("Cancelled".to_string());
73 running.set(false);
74 }
75 }
76 }
77
78 // Spawn the worker thread on first render
79 let worker_started = state!(cx, || false);
80 if !worker_started.get() {
81 worker_started.set(true);
82 let tx_progress = port.rx.tx();
83 if let Some(rx_commands) = port.take_outbound_rx() {
84 std::thread::spawn(move || {
85 worker_loop(tx_progress, rx_commands);
86 });
87 }
88 }
89
90 let start_task = {
91 let tx = port.tx();
92 with!(running => move || {
93 if !running.get() {
94 let _ = tx.send(TaskCommand::Start);
95 }
96 })
97 };
98
99 let cancel_task = {
100 let tx = port.tx();
101 with!(running => move || {
102 if running.get() {
103 let _ = tx.send(TaskCommand::Cancel);
104 }
105 })
106 };
107
108 let pct = progress.get();
109 let bar_width = 30usize;
110 let filled = (pct as usize * bar_width) / 100;
111 let bar = format!(
112 "[{}{}] {}%",
113 "█".repeat(filled),
114 "░".repeat(bar_width - filled),
115 pct
116 );
117
118 View::vstack()
119 .spacing(1)
120 .child(View::styled_text("Port: Background Task Runner").bold().build())
121 .child(
122 View::hstack()
123 .spacing(1)
124 .child(View::styled_text("Status:").dim().build())
125 .child(View::styled_text(status.get())
126 .color(if running.get() { Color::Yellow } else if pct == 100 { Color::Green } else { Color::Reset })
127 .bold()
128 .build())
129 .build(),
130 )
131 .child(View::styled_text(&bar).color(
132 if pct == 100 { Color::Green }
133 else if pct > 50 { Color::Yellow }
134 else { Color::Cyan }
135 ).build())
136 .child(if let Some(data) = result.get() {
137 View::vstack()
138 .child(View::styled_text("Result:").dim().build())
139 .child(View::styled_text(format!(" {}", data)).color(Color::Green).build())
140 .build()
141 } else {
142 View::empty()
143 })
144 .child(
145 View::hstack()
146 .spacing(1)
147 .child(
148 View::button()
149 .label(if running.get() { "[ Running... ]" } else { "[ Start Task ]" })
150 .on_press(start_task)
151 .build(),
152 )
153 .child(
154 View::button()
155 .label("[ Cancel ]")
156 .on_press(cancel_task)
157 .build(),
158 )
159 .build(),
160 )
161 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
162 .child(
163 View::modal()
164 .visible(show_help.get())
165 .title("Example 39: Port")
166 .on_dismiss(with!(show_help => move || show_help.set(false)))
167 .child(
168 View::vstack()
169 .child(View::styled_text("What you're seeing").bold().build())
170 .child(View::text("• Background task with progress"))
171 .child(View::text("• Bidirectional communication"))
172 .child(View::text("• Start and cancel controls"))
173 .child(View::gap(1))
174 .child(View::styled_text("Key concepts").bold().build())
175 .child(View::text("• port!(cx, InType, OutType)"))
176 .child(View::text("• port.rx.tx() sends to UI"))
177 .child(View::text("• port.tx() sends to worker"))
178 .child(View::text("• port.take_outbound_rx() for worker"))
179 .child(View::text("• port.rx.get() reads this frame"))
180 .child(View::gap(1))
181 .child(View::styled_text("Try this").bold().build())
182 .child(View::text("• Start a task, watch progress"))
183 .child(View::text("• Cancel mid-way"))
184 .child(View::text("• Start another after completion"))
185 .child(View::gap(1))
186 .child(View::styled_text("Press Escape to close").dim().build())
187 .build(),
188 )
189 .build(),
190 )
191 .build()
192 }Trait Implementations§
Auto Trait Implementations§
impl<In, Out> Freeze for PortHandle<In, Out>
impl<In, Out> !RefUnwindSafe for PortHandle<In, Out>
impl<In, Out> !Send for PortHandle<In, Out>
impl<In, Out> !Sync for PortHandle<In, Out>
impl<In, Out> Unpin for PortHandle<In, Out>
impl<In, Out> !UnwindSafe for PortHandle<In, Out>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.