Skip to main content

PortHandle

Struct PortHandle 

Source
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>

Source

pub fn new(wake: Arc<AtomicBool>) -> Self

Create a new bidirectional port with an event-loop wake flag.

Source

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    }
Source

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§

Source§

impl<In, Out> Clone for PortHandle<In, Out>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

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>

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)

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)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.