#![warn(missing_docs)]
use crate::{
brush::Brush,
color::draw_checker_board,
core::{
algebra::Vector2, color::Color, math::Rect, pool::Handle, reflect::prelude::*,
type_traits::prelude::*, variable::InheritableVariable, visitor::prelude::*,
},
draw::{CommandTexture, Draw, DrawingContext},
message::UiMessage,
widget::{Widget, WidgetBuilder},
BuildContext, Control, UiNode, UserInterface,
};
use crate::message::MessageData;
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use fyrox_texture::{TextureKind, TextureResource};
#[derive(Debug, Clone, PartialEq)]
pub enum ImageMessage {
Texture(Option<TextureResource>),
Flip(bool),
UvRect(Rect<f32>),
CheckerboardBackground(bool),
}
impl MessageData for ImageMessage {}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
#[type_uuid(id = "18e18d0f-cb84-4ac1-8050-3480a2ec3de5")]
#[visit(optional)]
#[reflect(derived_type = "UiNode")]
pub struct Image {
pub widget: Widget,
pub texture: InheritableVariable<Option<TextureResource>>,
pub flip: InheritableVariable<bool>,
pub uv_rect: InheritableVariable<Rect<f32>>,
pub checkerboard_background: InheritableVariable<bool>,
pub keep_aspect_ratio: InheritableVariable<bool>,
pub sync_with_texture_size: InheritableVariable<bool>,
}
impl ConstructorProvider<UiNode, UserInterface> for Image {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Image", |ui| {
ImageBuilder::new(
WidgetBuilder::new()
.with_height(32.0)
.with_width(32.0)
.with_name("Image"),
)
.build(&mut ui.build_ctx())
.to_base()
.into()
})
.with_group("Visual")
}
}
crate::define_widget_deref!(Image);
impl Control for Image {
fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
let mut size: Vector2<f32> = self.widget.measure_override(ui, available_size);
if *self.sync_with_texture_size {
if let Some(texture) = self.texture.as_ref() {
let state = texture.state();
if let Some(data) = state.data_ref() {
if let TextureKind::Rectangle { width, height } = data.kind() {
let width = width as f32;
let height = height as f32;
if *self.keep_aspect_ratio {
let aspect_ratio = width / height;
size.x = size.x.max(width).min(available_size.x);
size.y = size.x * aspect_ratio;
} else {
size.x = size.x.max(width);
size.y = size.y.max(height);
}
}
}
}
}
size
}
fn draw(&self, drawing_context: &mut DrawingContext) {
let bounds = self.widget.bounding_rect();
if *self.checkerboard_background {
draw_checker_board(
bounds,
self.clip_bounds(),
8.0,
&self.material,
drawing_context,
);
}
if self.texture.is_some() || !*self.checkerboard_background {
let tex_coords = if *self.flip {
Some([
Vector2::new(self.uv_rect.position.x, self.uv_rect.position.y),
Vector2::new(
self.uv_rect.position.x + self.uv_rect.size.x,
self.uv_rect.position.y,
),
Vector2::new(
self.uv_rect.position.x + self.uv_rect.size.x,
self.uv_rect.position.y - self.uv_rect.size.y,
),
Vector2::new(
self.uv_rect.position.x,
self.uv_rect.position.y - self.uv_rect.size.y,
),
])
} else {
Some([
Vector2::new(self.uv_rect.position.x, self.uv_rect.position.y),
Vector2::new(
self.uv_rect.position.x + self.uv_rect.size.x,
self.uv_rect.position.y,
),
Vector2::new(
self.uv_rect.position.x + self.uv_rect.size.x,
self.uv_rect.position.y + self.uv_rect.size.y,
),
Vector2::new(
self.uv_rect.position.x,
self.uv_rect.position.y + self.uv_rect.size.y,
),
])
};
drawing_context.push_rect_filled(&bounds, tex_coords.as_ref());
let texture = self
.texture
.as_ref()
.map_or(CommandTexture::None, |t| CommandTexture::Texture(t.clone()));
drawing_context.commit(
self.clip_bounds(),
self.widget.background(),
texture,
&self.material,
None,
);
}
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data::<ImageMessage>() {
if message.destination() == self.handle {
match msg {
ImageMessage::Texture(tex) => {
self.texture.set_value_and_mark_modified(tex.clone());
self.invalidate_visual();
}
&ImageMessage::Flip(flip) => {
self.flip.set_value_and_mark_modified(flip);
self.invalidate_visual();
}
ImageMessage::UvRect(uv_rect) => {
self.uv_rect.set_value_and_mark_modified(*uv_rect);
self.invalidate_visual();
}
ImageMessage::CheckerboardBackground(value) => {
self.checkerboard_background
.set_value_and_mark_modified(*value);
self.invalidate_visual();
}
}
}
}
}
}
pub struct ImageBuilder {
widget_builder: WidgetBuilder,
texture: Option<TextureResource>,
flip: bool,
uv_rect: Rect<f32>,
checkerboard_background: bool,
keep_aspect_ratio: bool,
sync_with_texture_size: bool,
}
impl ImageBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
texture: None,
flip: false,
uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
checkerboard_background: false,
keep_aspect_ratio: true,
sync_with_texture_size: true,
}
}
pub fn with_flip(mut self, flip: bool) -> Self {
self.flip = flip;
self
}
pub fn with_texture(mut self, texture: TextureResource) -> Self {
self.texture = Some(texture);
self
}
pub fn with_opt_texture(mut self, texture: Option<TextureResource>) -> Self {
self.texture = texture;
self
}
pub fn with_uv_rect(mut self, uv_rect: Rect<f32>) -> Self {
self.uv_rect = uv_rect;
self
}
pub fn with_checkerboard_background(mut self, checkerboard_background: bool) -> Self {
self.checkerboard_background = checkerboard_background;
self
}
pub fn with_keep_aspect_ratio(mut self, keep_aspect_ratio: bool) -> Self {
self.keep_aspect_ratio = keep_aspect_ratio;
self
}
pub fn with_sync_with_texture_size(mut self, sync_with_texture_size: bool) -> Self {
self.sync_with_texture_size = sync_with_texture_size;
self
}
pub fn build_image(mut self, ctx: &BuildContext) -> Image {
if self.widget_builder.background.is_none() {
self.widget_builder.background = Some(Brush::Solid(Color::WHITE).into())
}
Image {
widget: self.widget_builder.build(ctx),
texture: self.texture.into(),
flip: self.flip.into(),
uv_rect: self.uv_rect.into(),
checkerboard_background: self.checkerboard_background.into(),
keep_aspect_ratio: self.keep_aspect_ratio.into(),
sync_with_texture_size: self.sync_with_texture_size.into(),
}
}
pub fn build_node(self, ctx: &BuildContext) -> UiNode {
UiNode::new(self.build_image(ctx))
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<Image> {
ctx.add(self.build_image(ctx))
}
}
#[cfg(test)]
mod test {
use crate::image::ImageBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| ImageBuilder::new(WidgetBuilder::new()).build(ctx));
}
}