use crate::{Control, ControlImpl};
use ferroui_base::input::InputElementImpl;
use ferroui_base::interactivity::InteractiveImpl;
use ferroui_base::layout::{Layoutable, LayoutableImpl};
use ferroui_base::media::imaging::BitmapBlendingMode;
use ferroui_base::media::{
DrawingContext, DrawingImage, IImage, MediaExtensions, RenderOptions, Stretch, StretchDirection,
};
use ferroui_base::reactive::IDisposable;
use ferroui_base::{
ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
FerroPropertyChangedEventArgs, Rect, Ref, Size, StyledElementImpl, StyledProperty, Upcast, Visual, VisualImpl,
VisualImplExt, VisualTreeAttachmentEventArgs,
};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
#[repr(C)]
pub struct Image {
base: Control,
current_drawing_bounds: Cell<Rect>,
subscribed_to_drawing_image_source: Cell<bool>,
source_invalidated_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
}
ferro_class!(Image: Control);
ferroui_base::ferro_class_info!(Image { new: Image::new });
ferro_impl_classes!(Image: StyledElementImpl, InteractiveImpl, InputElementImpl);
impl ControlImpl for Image {
fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
crate::automation::peers::ImageAutomationPeer::new(this).upcast()
}
}
impl FerroObjectImpl for Image {
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed(this, change);
if change.property() == Self::source_property().as_property() {
let (old_value, new_value) = change.get_old_and_new_value::<Option<Rc<dyn IImage>>>();
this.current_drawing_bounds.set(Rect::default());
if Self::as_drawing_image(old_value.as_ref()).is_some() && this.subscribed_to_drawing_image_source.get() {
this.unsubscribe_from_source();
}
if let Some(new_drawing_image) = Self::as_drawing_image(new_value.as_ref()) {
if this.is_attached_to_visual_tree() {
this.subscribe_to_source(&new_drawing_image);
}
}
}
}
}
impl VisualImpl for Image {
fn render(this: &Self, context: &mut DrawingContext) {
let source = this.source();
let bounds = this.bounds();
if let Some(source) = source {
if bounds.width > 0.0 && bounds.height > 0.0 {
let view_port = Rect::from_size(bounds.size());
let source_size = source.size();
let scale = MediaExtensions::calculate_scaling(
this.stretch(),
bounds.size(),
source_size,
this.stretch_direction(),
);
let scaled_size = source_size * scale;
let dest_rect = view_port.center_rect(Rect::from_size(scaled_size)).intersect(view_port);
let source_rect =
Rect::from_size(source_size).center_rect(Rect::from_size(dest_rect.size() / scale));
let visual: &Visual = this.upcast();
let render_options = RenderOptions {
text_rendering_mode: RenderOptions::get_text_rendering_mode(visual),
bitmap_interpolation_mode: RenderOptions::get_bitmap_interpolation_mode(visual),
edge_mode: RenderOptions::get_edge_mode(visual),
bitmap_blending_mode: this.blend_mode(),
requires_full_opacity_handling: RenderOptions::get_requires_full_opacity_handling(visual),
};
let state = context.push_render_options(render_options);
context.draw_image_with_rects(&*source, source_rect, dest_rect);
context.pop(state);
}
}
}
fn bypass_flow_direction_policies(_this: &Self) -> bool {
true
}
fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
Self::parent_on_attached_to_visual_tree(this, e);
if !this.subscribed_to_drawing_image_source.get() {
if let Some(drawing_image) = Self::as_drawing_image(this.source().as_ref()) {
this.subscribe_to_source(&drawing_image);
}
}
}
fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
Self::parent_on_detached_from_visual_tree(this, e);
if this.subscribed_to_drawing_image_source.get() && Self::as_drawing_image(this.source().as_ref()).is_some() {
this.unsubscribe_from_source();
}
}
}
impl LayoutableImpl for Image {
fn measure_override(this: &Self, available_size: Size) -> Size {
let source = this.source();
let mut result = Size::default();
if let Some(source) = source {
result = MediaExtensions::calculate_size(
this.stretch(),
available_size,
source.size(),
this.stretch_direction(),
);
}
result
}
fn arrange_override(this: &Self, final_size: Size) -> Size {
let source = this.source();
if let Some(source) = source {
let source_size = source.size();
MediaExtensions::calculate_size(this.stretch(), final_size, source_size, StretchDirection::Both)
} else {
Size::default()
}
}
}
ferroui_base::ferro_properties! { impl Image {
ferro_property!(
pub fn source_property() -> StyledProperty<Option<Rc<dyn IImage>>> {
FerroProperty::register::<Image, _>("Source", None)
}
);
ferro_property!(
pub fn blend_mode_property() -> StyledProperty<BitmapBlendingMode> {
FerroProperty::register::<Image, _>("BlendMode", BitmapBlendingMode::Unspecified)
}
);
ferro_property!(
pub fn stretch_property() -> StyledProperty<Stretch> {
FerroProperty::register::<Image, _>("Stretch", Stretch::Uniform)
}
);
ferro_property!(
pub fn stretch_direction_property() -> StyledProperty<StretchDirection> {
FerroProperty::register::<Image, _>("StretchDirection", StretchDirection::Both)
}
);
} }
impl Image {
fn static_constructor() {
Visual::affects_render::<Image>(&[
Self::source_property().as_property(),
Self::stretch_property().as_property(),
Self::stretch_direction_property().as_property(),
Self::blend_mode_property().as_property(),
]);
Layoutable::affects_measure::<Image>(&[
Self::source_property().as_property(),
Self::stretch_property().as_property(),
Self::stretch_direction_property().as_property(),
]);
crate::automation::AutomationProperties::control_type_override_property()
.override_default_value::<Image>(Some(crate::automation::peers::AutomationControlType::Image));
}
pub fn construct() -> Self {
Self {
base: Control::construct(),
current_drawing_bounds: Cell::new(Rect::default()),
subscribed_to_drawing_image_source: Cell::new(false),
source_invalidated_subscription: RefCell::new(None),
}
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn source(&self) -> Option<Rc<dyn IImage>> {
self.get_value(Self::source_property())
}
pub fn set_source(&self, value: Option<Rc<dyn IImage>>) {
self.set_value(Self::source_property(), value)
}
pub fn blend_mode(&self) -> BitmapBlendingMode {
self.get_value(Self::blend_mode_property())
}
pub fn set_blend_mode(&self, value: BitmapBlendingMode) {
self.set_value(Self::blend_mode_property(), value)
}
pub fn stretch(&self) -> Stretch {
self.get_value(Self::stretch_property())
}
pub fn set_stretch(&self, value: Stretch) {
self.set_value(Self::stretch_property(), value)
}
pub fn stretch_direction(&self) -> StretchDirection {
self.get_value(Self::stretch_direction_property())
}
pub fn set_stretch_direction(&self, value: StretchDirection) {
self.set_value(Self::stretch_direction_property(), value)
}
fn as_drawing_image(image: Option<&Rc<dyn IImage>>) -> Option<Ref<DrawingImage>> {
image?.as_object()?.downcast_ref::<DrawingImage>().map(DrawingImage::to_ref)
}
fn subscribe_to_source(&self, drawing_image: &Ref<DrawingImage>) {
self.subscribed_to_drawing_image_source.set(true);
let weak = self.to_ref().downgrade();
let subscription = drawing_image.invalidated(move || {
if let Some(this) = weak.upgrade() {
this.on_source_invalidated();
}
});
let previous = self.source_invalidated_subscription.replace(Some(subscription));
if let Some(previous) = previous {
previous.dispose();
}
}
fn unsubscribe_from_source(&self) {
self.subscribed_to_drawing_image_source.set(false);
let subscription = self.source_invalidated_subscription.take();
if let Some(subscription) = subscription {
subscription.dispose();
}
}
fn on_source_invalidated(&self) {
if !self.is_attached_to_visual_tree() {
return;
}
let Some(drawing_image) = Self::as_drawing_image(self.source().as_ref()) else {
return;
};
if let Some(drawing) = drawing_image.drawing() {
let bounds = drawing.get_bounds();
if bounds != self.current_drawing_bounds.get() {
self.invalidate_measure();
}
self.current_drawing_bounds.set(bounds);
}
}
}