Skip to main content

ferroui_controls/remote/
remote_widget.rs

1use crate::remote::UiThreadHandle;
2use crate::{Control, ControlImpl};
3use ferroui_base::input::InputElementImpl;
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
6use ferroui_base::media::imaging::WriteableBitmap;
7use ferroui_base::media::DrawingContext;
8use ferroui_base::platform::{PixelFormat, PixelFormats};
9use ferroui_base::threading::DispatcherPriority;
10use ferroui_base::{
11    ferro_class, ferro_impl_classes, instantiate, FerroObjectImpl, FerroObjectImplExt, PixelSize, Rect, Ref,
12    StyledElementImpl, Vector, VisualImpl, VisualImplExt, WeakRef,
13};
14use ferroui_remote_protocol::viewport::{
15    ClientSupportedPixelFormatsMessage, ClientViewportAllocatedMessage, FrameMessage, FrameReceivedMessage,
16    PixelFormat as ProtocolPixelFormat,
17};
18use ferroui_remote_protocol::{message_handler, IFerroRemoteTransportConnection, Message};
19use std::cell::{Cell, RefCell};
20use std::sync::Arc;
21
22/// `RemoteWidget.SizingMode`.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24pub enum SizingMode {
25    Local,
26    Remote,
27}
28
29/// Shows the frames received from a connection of the remote protocol.
30/// (Internal upstream; public here for the crates the upstream assembly is
31/// visible to.)
32#[repr(C)]
33pub struct RemoteWidget {
34    base: Control,
35    connection: Arc<dyn IFerroRemoteTransportConnection>,
36    /// `_lastFrame`: the message as it was received, which is a
37    /// [`FrameMessage`].
38    last_frame: RefCell<Option<Message>>,
39    bitmap: RefCell<Option<WriteableBitmap>>,
40    mode: Cell<SizingMode>,
41}
42
43ferro_class!(RemoteWidget: Control);
44ferroui_base::ferro_class_info!(RemoteWidget {});
45ferro_impl_classes!(RemoteWidget: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
46
47/// `new PixelFormat((PixelFormatEnum)format)`: the first three members of
48/// the two enumerations are the same.
49fn platform_pixel_format(format: ProtocolPixelFormat) -> PixelFormat {
50    match format {
51        ProtocolPixelFormat::Rgb565 => PixelFormats::RGB565,
52        ProtocolPixelFormat::Rgba8888 => PixelFormats::RGBA8888,
53        ProtocolPixelFormat::Bgra8888 => PixelFormats::BGRA8888,
54    }
55}
56
57impl FerroObjectImpl for RemoteWidget {
58    fn constructed(this: &Self) {
59        Self::parent_constructed(this);
60
61        // `_connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(OnMessage, msg)`:
62        // the handler runs on the reader thread of the connection and posts
63        // the message, which is plain data, to the UI thread, where the
64        // widget is looked up again.
65        let handle: UiThreadHandle<WeakRef<RemoteWidget>> =
66            UiThreadHandle::register(this.to_ref().downgrade(), |widget| widget.upgrade().is_some());
67        this.connection.on_message(message_handler(move |_, msg| {
68            let msg = msg.clone();
69            handle.post(
70                move |widget| {
71                    if let Some(widget) = widget.upgrade() {
72                        widget.on_message(&msg);
73                    }
74                },
75                DispatcherPriority::DEFAULT,
76            );
77        }));
78        this.connection.send(Arc::new(ClientSupportedPixelFormatsMessage {
79            formats: Some(vec![ProtocolPixelFormat::Bgra8888, ProtocolPixelFormat::Rgba8888]),
80        }));
81    }
82}
83
84impl LayoutableImpl for RemoteWidget {
85    fn arrange_core(this: &Self, final_rect: Rect) {
86        if this.mode.get() == SizingMode::Local {
87            this.connection.send(Arc::new(ClientViewportAllocatedMessage {
88                width: final_rect.width,
89                height: final_rect.height,
90                dpi_x: 10.0 * 96.0,
91                dpi_y: 10.0 * 96.0, //TODO: Somehow detect the actual DPI
92            }));
93        }
94
95        Self::parent_arrange_core(this, final_rect);
96    }
97}
98
99impl VisualImpl for RemoteWidget {
100    fn render(this: &Self, context: &mut DrawingContext) {
101        let last_frame = this.last_frame.borrow().clone();
102        let last_frame = last_frame.as_ref().and_then(|frame| frame.downcast_ref::<FrameMessage>());
103        if let Some(last_frame) = last_frame {
104            if last_frame.width != 0 && last_frame.height != 0 {
105                let fmt = platform_pixel_format(last_frame.format);
106                let mut bitmap = this.bitmap.borrow_mut();
107                let stale = match bitmap.as_ref() {
108                    None => true,
109                    Some(bitmap) => {
110                        bitmap.pixel_size().width != last_frame.width || bitmap.pixel_size().height != last_frame.height
111                    }
112                };
113                if stale {
114                    if let Some(old) = bitmap.take() {
115                        old.dispose();
116                    }
117                    *bitmap = Some(WriteableBitmap::new(
118                        PixelSize::new(last_frame.width, last_frame.height),
119                        Vector::new(96.0, 96.0),
120                        Some(fmt),
121                        None,
122                    ));
123                }
124                let bitmap = bitmap.as_ref().expect("the bitmap was created above");
125                {
126                    let l = bitmap.lock();
127                    let line_len = ((if fmt == PixelFormat::RGB565 { 2 } else { 4 }) * last_frame.width) as usize;
128                    let row_bytes = l.row_bytes() as usize;
129                    let stride = last_frame.stride as usize;
130                    // `Marshal.Copy` of a null array is an
131                    // `ArgumentNullException`, and a frame shorter than it
132                    // says is out of range: both panic here.
133                    let data = last_frame.data.as_deref().expect("Value cannot be null. (Parameter 'source')");
134                    l.with_data(&mut |dest| {
135                        for y in 0..last_frame.height as usize {
136                            dest[row_bytes * y..row_bytes * y + line_len]
137                                .copy_from_slice(&data[y * stride..y * stride + line_len]);
138                        }
139                    });
140                    l.dispose();
141                }
142                context.draw_image_with_rects(
143                    bitmap,
144                    Rect::new(0.0, 0.0, f64::from(bitmap.pixel_size().width), f64::from(bitmap.pixel_size().height)),
145                    Rect::from_size(this.bounds().size()),
146                );
147            }
148        }
149        Self::parent_render(this, context);
150    }
151}
152
153impl RemoteWidget {
154    /// Field initialisation only.
155    pub fn construct(connection: Arc<dyn IFerroRemoteTransportConnection>) -> Self {
156        Self {
157            base: Control::construct(),
158            connection,
159            last_frame: RefCell::new(None),
160            bitmap: RefCell::new(None),
161            mode: Cell::new(SizingMode::Local),
162        }
163    }
164
165    /// `new RemoteWidget(connection)`.
166    pub fn new(connection: Arc<dyn IFerroRemoteTransportConnection>) -> Ref<Self> {
167        instantiate(Self::construct(connection))
168    }
169
170    pub fn mode(&self) -> SizingMode {
171        self.mode.get()
172    }
173
174    pub fn set_mode(&self, value: SizingMode) {
175        self.mode.set(value)
176    }
177
178    fn on_message(&self, msg: &Message) {
179        if let Some(frame) = msg.downcast_ref::<FrameMessage>() {
180            self.connection.send(Arc::new(FrameReceivedMessage { sequence_id: frame.sequence_id }));
181            *self.last_frame.borrow_mut() = Some(msg.clone());
182            self.invalidate_visual();
183        }
184    }
185
186    /// The last frame received (`_lastFrame`).
187    #[cfg(test)]
188    pub(crate) fn last_frame(&self) -> Option<Message> {
189        self.last_frame.borrow().clone()
190    }
191}