1use std::{
2 any::Any,
3 borrow::Cow,
4 collections::HashMap,
5 rc::Rc,
6};
7
8use freya_components::loader::CircularLoader;
9use freya_core::{
10 elements::image::{
11 AspectRatio,
12 ImageData,
13 SamplingMode,
14 },
15 integration::*,
16 prelude::*,
17};
18use freya_engine::prelude::{
19 ClipOp,
20 CubicResampler,
21 FilterMode,
22 MipmapMode,
23 Paint,
24 SamplingOptions,
25 SkRect,
26};
27use torin::prelude::Size2D;
28
29use crate::{
30 PlaybackState,
31 VideoFrame,
32 VideoPlayer,
33};
34
35#[derive(PartialEq)]
37pub struct VideoViewer {
38 player: VideoPlayer,
39
40 layout: LayoutData,
41 image_data: ImageData,
42 accessibility: AccessibilityData,
43
44 key: DiffKey,
45}
46
47impl VideoViewer {
48 pub fn new(player: VideoPlayer) -> Self {
49 Self {
50 player,
51 layout: LayoutData::default(),
52 image_data: ImageData::default(),
53 accessibility: AccessibilityData::default(),
54 key: DiffKey::None,
55 }
56 }
57}
58
59impl KeyExt for VideoViewer {
60 fn write_key(&mut self) -> &mut DiffKey {
61 &mut self.key
62 }
63}
64
65impl LayoutExt for VideoViewer {
66 fn get_layout(&mut self) -> &mut LayoutData {
67 &mut self.layout
68 }
69}
70
71impl ContainerSizeExt for VideoViewer {}
72
73impl ImageExt for VideoViewer {
74 fn get_image_data(&mut self) -> &mut ImageData {
75 &mut self.image_data
76 }
77}
78
79impl AccessibilityExt for VideoViewer {
80 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
81 &mut self.accessibility
82 }
83}
84
85impl Component for VideoViewer {
86 fn render(&self) -> impl IntoElement {
87 match (self.player.frame(), self.player.state()) {
88 (Some(frame), _) => video(frame)
89 .accessibility(self.accessibility.clone())
90 .a11y_role(AccessibilityRole::Video)
91 .a11y_focusable(true)
92 .layout(self.layout.clone())
93 .image_data(self.image_data.clone())
94 .into_element(),
95 (None, PlaybackState::Errored) => "Failed to decode video".into_element(),
96 (None, _) => rect()
97 .layout(self.layout.clone())
98 .center()
99 .child(CircularLoader::new())
100 .into(),
101 }
102 }
103
104 fn render_key(&self) -> DiffKey {
105 self.key.clone().or(self.default_key())
106 }
107}
108
109pub fn video(frame: VideoFrame) -> Video {
111 Video {
112 key: DiffKey::None,
113 element: VideoElement {
114 frame,
115 accessibility: AccessibilityData::default(),
116 layout: LayoutData::default(),
117 event_handlers: HashMap::default(),
118 image_data: ImageData::default(),
119 },
120 }
121}
122
123pub struct Video {
124 key: DiffKey,
125 element: VideoElement,
126}
127
128impl Video {
129 pub fn try_downcast(element: &dyn ElementExt) -> Option<VideoElement> {
130 (element as &dyn Any)
131 .downcast_ref::<VideoElement>()
132 .cloned()
133 }
134}
135
136impl From<Video> for Element {
137 fn from(value: Video) -> Self {
138 Element::Element {
139 key: value.key,
140 element: Rc::new(value.element),
141 elements: vec![],
142 }
143 }
144}
145
146impl KeyExt for Video {
147 fn write_key(&mut self) -> &mut DiffKey {
148 &mut self.key
149 }
150}
151
152impl LayoutExt for Video {
153 fn get_layout(&mut self) -> &mut LayoutData {
154 &mut self.element.layout
155 }
156}
157
158impl ContainerExt for Video {}
159
160impl ImageExt for Video {
161 fn get_image_data(&mut self) -> &mut ImageData {
162 &mut self.element.image_data
163 }
164}
165
166impl AccessibilityExt for Video {
167 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
168 &mut self.element.accessibility
169 }
170}
171
172impl EventHandlersExt for Video {
173 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
174 &mut self.element.event_handlers
175 }
176}
177
178impl MaybeExt for Video {}
179
180#[derive(Clone)]
181pub struct VideoElement {
182 accessibility: AccessibilityData,
183 layout: LayoutData,
184 event_handlers: FxHashMap<EventName, EventHandlerType>,
185 frame: VideoFrame,
186 image_data: ImageData,
187}
188
189impl PartialEq for VideoElement {
190 fn eq(&self, other: &Self) -> bool {
191 self.accessibility == other.accessibility
192 && self.layout == other.layout
193 && self.image_data == other.image_data
194 && self.frame == other.frame
195 }
196}
197
198impl ElementExt for VideoElement {
199 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
200 let Some(video) = (other.as_ref() as &dyn Any).downcast_ref::<VideoElement>() else {
201 return false;
202 };
203 self != video
204 }
205
206 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
207 let Some(video) = (other.as_ref() as &dyn Any).downcast_ref::<VideoElement>() else {
208 return DiffModifies::all();
209 };
210
211 let mut diff = DiffModifies::empty();
212
213 if self.accessibility != video.accessibility {
214 diff.insert(DiffModifies::ACCESSIBILITY);
215 }
216
217 if self.layout != video.layout {
218 diff.insert(DiffModifies::LAYOUT);
219 }
220
221 if self.frame != video.frame {
222 diff.insert(DiffModifies::LAYOUT);
223 diff.insert(DiffModifies::STYLE);
224 }
225
226 diff
227 }
228
229 fn layout(&'_ self) -> Cow<'_, LayoutData> {
230 Cow::Borrowed(&self.layout)
231 }
232
233 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
234 None
235 }
236
237 fn style(&'_ self) -> Cow<'_, StyleState> {
238 Cow::Owned(StyleState::default())
239 }
240
241 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
242 Cow::Owned(TextStyleData::default())
243 }
244
245 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
246 Cow::Borrowed(&self.accessibility)
247 }
248
249 fn should_measure_inner_children(&self) -> bool {
250 false
251 }
252
253 fn should_hook_measurement(&self) -> bool {
254 true
255 }
256
257 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
258 let image = self.frame.image();
259 let image_width = image.width() as f32;
260 let image_height = image.height() as f32;
261
262 let width_ratio = context.area_size.width / image_width;
263 let height_ratio = context.area_size.height / image_height;
264 let scaled = |ratio: f32| Size2D::new(image_width * ratio, image_height * ratio);
265
266 let size = match self.image_data.aspect_ratio {
267 AspectRatio::Max => scaled(width_ratio.max(height_ratio)),
268 AspectRatio::Min => scaled(width_ratio.min(height_ratio)),
269 AspectRatio::Fit => Size2D::new(image_width, image_height),
270 AspectRatio::None => *context.area_size,
271 };
272
273 Some((size, Rc::new(size)))
274 }
275
276 fn clip(&self, context: ClipContext) {
277 let area = context.visible_area;
278 context.canvas.clip_rect(
279 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
280 ClipOp::Intersect,
281 true,
282 );
283 }
284
285 fn render(&self, context: RenderContext) {
286 let size = context
287 .layout_node
288 .data
289 .as_ref()
290 .unwrap()
291 .downcast_ref::<Size2D>()
292 .unwrap();
293 let area = context.layout_node.visible_area();
294
295 let mut rect = SkRect::new(
296 area.min_x(),
297 area.min_y(),
298 area.min_x() + size.width,
299 area.min_y() + size.height,
300 );
301 if self.image_data.image_cover == ImageCover::Center {
302 let offset_x = (size.width - area.width()) / 2.;
303 let offset_y = (size.height - area.height()) / 2.;
304 rect.left -= offset_x;
305 rect.right -= offset_x;
306 rect.top -= offset_y;
307 rect.bottom -= offset_y;
308 }
309
310 let mut paint = Paint::default();
311 paint.set_anti_alias(true);
312
313 context.canvas.save();
314 context.canvas.clip_rect(
315 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
316 ClipOp::Intersect,
317 true,
318 );
319 context.canvas.draw_image_rect_with_sampling_options(
320 self.frame.image(),
321 None,
322 rect,
323 sampling_options(&self.image_data.sampling_mode),
324 &paint,
325 );
326 context.canvas.restore();
327 }
328}
329
330fn sampling_options(mode: &SamplingMode) -> SamplingOptions {
331 match mode {
332 SamplingMode::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
333 SamplingMode::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
334 SamplingMode::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
335 SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
336 SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
337 }
338}