use frust_core::accesskit::Role;
use frust_core::{
BoxConstraints, BuildCtx, ChangeFlags, LayoutCtx, PaintCtx, PaintScene, SemanticsCtx, View,
Widget,
};
use kurbo::{Rect, Size};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TextureFit {
#[default]
Contain,
Cover,
Fill,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum TextureSize {
Expand,
Explicit(Size),
}
#[must_use]
pub fn texture_fit_rect(natural: Size, dest: Size, fit: TextureFit) -> Rect {
let degenerate = !(natural.width > 0.0
&& natural.height > 0.0
&& dest.width > 0.0
&& dest.height > 0.0
&& natural.width.is_finite()
&& natural.height.is_finite());
let fit_width = dest.width / natural.width;
let fit_height = dest.height / natural.height;
let scale = match fit {
_ if degenerate => return Rect::from_origin_size((0.0, 0.0), dest),
TextureFit::Fill => return Rect::from_origin_size((0.0, 0.0), dest),
TextureFit::Contain => fit_width.min(fit_height),
TextureFit::Cover => fit_width.max(fit_height),
};
let width = natural.width * scale;
let height = natural.height * scale;
let x = (dest.width - width) / 2.0;
let y = (dest.height - height) / 2.0;
Rect::new(x, y, x + width, y + height)
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextureView {
id: u64,
fit: TextureFit,
source_size: Option<Size>,
size: TextureSize,
corner_radius: f64,
label: Option<String>,
}
#[must_use]
pub fn texture_view(scene_texture_id: u64) -> TextureView {
TextureView {
id: scene_texture_id,
fit: TextureFit::default(),
source_size: None,
size: TextureSize::Expand,
corner_radius: 0.0,
label: None,
}
}
impl TextureView {
#[must_use]
pub fn fit(mut self, fit: TextureFit) -> Self {
self.fit = fit;
self
}
#[must_use]
pub fn source_size(mut self, size: Size) -> Self {
self.source_size = Some(size);
self
}
#[must_use]
pub fn size(mut self, size: Size) -> Self {
self.size = TextureSize::Explicit(size);
self
}
#[must_use]
pub fn expand(mut self) -> Self {
self.size = TextureSize::Expand;
self
}
#[must_use]
pub fn corner_radius(mut self, radius: f64) -> Self {
self.corner_radius = radius.max(0.0);
self
}
#[must_use]
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
}
impl<State: 'static> View<State> for TextureView {
type Element = TextureWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> TextureWidget {
TextureWidget { view: self.clone() }
}
fn rebuild(
&self,
prev: &Self,
element: &mut TextureWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
if prev == self {
return ChangeFlags::NONE;
}
let mut flags = ChangeFlags::PAINT;
if prev.size != self.size {
flags |= ChangeFlags::LAYOUT;
}
element.view = self.clone();
flags
}
}
pub struct TextureWidget {
view: TextureView,
}
impl TextureWidget {
#[must_use]
pub fn scene_texture_id(&self) -> u64 {
self.view.id
}
}
impl Widget for TextureWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
match self.view.size {
TextureSize::Explicit(size) => bc.constrain(size),
TextureSize::Expand => bc.max(),
}
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
let origin = ctx.origin();
let size = ctx.size();
let natural = self.view.source_size.unwrap_or(size);
let local = texture_fit_rect(natural, size, self.view.fit);
let dest = local + origin.to_vec2();
let overflows =
local.x0 < 0.0 || local.y0 < 0.0 || local.x1 > size.width || local.y1 > size.height;
let clip = if self.view.corner_radius > 0.0 {
scene.push_clip_rounded(origin, size, self.view.corner_radius);
true
} else if overflows {
scene.push_clip(origin, size);
true
} else {
false
};
scene.draw_scene_texture(self.view.id, dest);
if clip {
scene.pop_clip();
}
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
if let Some(label) = &self.view.label {
ctx.push_node(Role::Image, |node| {
node.set_label(label.as_str());
});
}
}
}