zng_wgt_image/
border.rs

1//! 9-patch image border.
2
3use std::mem;
4
5use zng_app::render::RepeatMode;
6use zng_ext_image::{IMAGES, ImageCacheMode, ImageRenderArgs, ImageSource, Img};
7use zng_wgt::prelude::*;
8
9use crate::{IMAGE_CACHE_VAR, IMAGE_LIMITS_VAR, IMAGE_RENDERING_VAR};
10
11/// 9-patch image border.
12///
13/// The `source` image is sliced by `slices` and projected onto `widths` in the widget layout.
14///
15/// See also [`border_img_repeat`] and [`border_img_fill`].
16///
17/// [`border_img_repeat`]: fn@border_img_repeat
18/// [`border_img_fill`]: fn@border_img_fill
19#[property(BORDER)]
20pub fn border_img(
21    child: impl UiNode,
22    widths: impl IntoVar<SideOffsets>,
23    source: impl IntoVar<ImageSource>,
24    slices: impl IntoVar<SideOffsets>,
25) -> impl UiNode {
26    let widths = widths.into_var();
27    let source = source.into_var();
28    let slices = slices.into_var();
29
30    let mut img = var(Img::dummy(None)).read_only();
31    let mut _img_sub = VarHandle::dummy();
32    let mut slices_img_size = PxSize::zero();
33    let mut slices_px = PxSideOffsets::zero();
34
35    border_node(
36        child,
37        widths,
38        match_node_leaf(move |op| match op {
39            UiNodeOp::Init => {
40                WIDGET
41                    .sub_var(&source)
42                    .sub_var(&IMAGE_CACHE_VAR)
43                    .sub_var_render(&slices)
44                    .sub_var_render(&BORDER_IMG_REPEAT_VAR)
45                    .sub_var_render(&IMAGE_RENDERING_VAR)
46                    .sub_var_render(&BORDER_IMG_FILL_VAR);
47
48                let mode = if IMAGE_CACHE_VAR.get() {
49                    ImageCacheMode::Cache
50                } else {
51                    ImageCacheMode::Ignore
52                };
53                let limits = IMAGE_LIMITS_VAR.get();
54
55                let mut source = source.get();
56                if let ImageSource::Render(_, args) = &mut source {
57                    *args = Some(ImageRenderArgs { parent: Some(WINDOW.id()) });
58                }
59                img = IMAGES.image(source, mode, limits, None, None);
60                _img_sub = img.subscribe(UpdateOp::Update, WIDGET.id());
61
62                let img = img.get();
63                if img.is_loaded() {
64                    slices_img_size = img.size();
65                }
66            }
67            UiNodeOp::Deinit => {
68                img = var(Img::dummy(None)).read_only();
69                _img_sub = VarHandle::dummy();
70            }
71            UiNodeOp::Update { .. } => {
72                if source.is_new() {
73                    // source update:
74
75                    let mut source = source.get();
76
77                    if let ImageSource::Render(_, args) = &mut source {
78                        *args = Some(ImageRenderArgs { parent: Some(WINDOW.id()) });
79                    }
80
81                    let mode = if IMAGE_CACHE_VAR.get() {
82                        ImageCacheMode::Cache
83                    } else {
84                        ImageCacheMode::Ignore
85                    };
86                    let limits = IMAGE_LIMITS_VAR.get();
87
88                    img = IMAGES.image(source, mode, limits, None, None);
89                } else if let Some(enabled) = IMAGE_CACHE_VAR.get_new() {
90                    // cache-mode update:
91                    let is_cached = img.with(|img| IMAGES.is_cached(img));
92                    if enabled != is_cached {
93                        img = if is_cached {
94                            // must not cache, but is cached, detach from cache.
95
96                            let img = mem::replace(&mut img, var(Img::dummy(None)).read_only());
97                            IMAGES.detach(img)
98                        } else {
99                            // must cache, but image is not cached, get source again.
100
101                            let source = source.get();
102                            let limits = IMAGE_LIMITS_VAR.get();
103                            IMAGES.image(source, ImageCacheMode::Cache, limits, None, None)
104                        };
105                    }
106                }
107
108                if img.is_new() {
109                    let img = img.get();
110                    if img.is_loaded() {
111                        let s = img.size();
112                        if s != slices_img_size {
113                            slices_img_size = s;
114                            WIDGET.layout();
115                        }
116                    }
117                    WIDGET.render();
118                }
119            }
120            UiNodeOp::Measure { desired_size, .. } => {
121                *desired_size = LAYOUT.constraints().fill_size();
122            }
123            UiNodeOp::Layout { final_size, .. } => {
124                *final_size = LAYOUT.constraints().fill_size();
125
126                let metrics = LAYOUT
127                    .metrics()
128                    .with_constraints(PxConstraints2d::new_exact_size(slices_img_size))
129                    .with_scale_factor(1.fct());
130                let s = LAYOUT.with_context(metrics, || slices.layout());
131                if s != slices_px {
132                    slices_px = s;
133                    WIDGET.render();
134                }
135            }
136            UiNodeOp::Render { frame } => {
137                let img = img.get();
138                if img.is_loaded() {
139                    let (rect, offsets) = BORDER.border_layout();
140                    if !rect.size.is_empty() {
141                        let repeats = BORDER_IMG_REPEAT_VAR.get();
142                        frame.push_border_image(
143                            rect,
144                            offsets,
145                            slices_px,
146                            BORDER_IMG_FILL_VAR.get(),
147                            repeats.top_bottom,
148                            repeats.left_right,
149                            &img,
150                            IMAGE_RENDERING_VAR.get(),
151                        );
152                    }
153                }
154            }
155            _ => {}
156        }),
157    )
158}
159
160/// Defines how the 9-patch edge slices are used to fill the widths.
161///
162/// This property sets the [`BORDER_IMG_REPEAT_VAR`].
163#[property(CONTEXT, default(BORDER_IMG_REPEAT_VAR))]
164pub fn border_img_repeat(child: impl UiNode, repeats: impl IntoVar<BorderRepeats>) -> impl UiNode {
165    with_context_var(child, BORDER_IMG_REPEAT_VAR, repeats)
166}
167
168/// Defines if the middle slice of the 9-patch image is also rendered.
169///
170/// This property sets the [`BORDER_IMG_FILL_VAR`].
171#[property(CONTEXT, default(BORDER_IMG_FILL_VAR))]
172pub fn border_img_fill(child: impl UiNode, fill: impl IntoVar<bool>) -> impl UiNode {
173    with_context_var(child, BORDER_IMG_FILL_VAR, fill)
174}
175
176/// Defines how the 9-patch edge slices are used to fill the widths.
177#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
178pub struct BorderRepeats {
179    /// Top and bottom edges.
180    ///
181    /// Also middle if *fill* is set.
182    pub top_bottom: RepeatMode,
183    /// Left and right edges.
184    pub left_right: RepeatMode,
185}
186impl BorderRepeats {
187    /// Top-bottom and left-right equal. From any [`RepeatMode`] type.
188    pub fn new<TB: Into<RepeatMode>, LR: Into<RepeatMode>>(top_bottom: TB, left_right: LR) -> Self {
189        BorderRepeats {
190            top_bottom: top_bottom.into(),
191            left_right: left_right.into(),
192        }
193    }
194
195    /// All sides equal. From any [`RepeatMode`] type.
196    pub fn new_all<T: Into<RepeatMode>>(all_sides: T) -> Self {
197        let all_sides = all_sides.into();
198        Self::new(all_sides, all_sides)
199    }
200}
201context_var! {
202    /// Defines how the 9-patch edge slices are used to fill the widths.
203    pub static BORDER_IMG_REPEAT_VAR: BorderRepeats = BorderRepeats::default();
204
205    /// If the middle slice is rendered in a 9-patch border.
206    pub static BORDER_IMG_FILL_VAR: bool = false;
207}
208
209impl_from_and_into_var! {
210    fn from(repeat: RepeatMode) -> BorderRepeats {
211        BorderRepeats::new_all(repeat)
212    }
213
214    /// `true` is `Repeat`, `false` is `Stretch`.
215    fn from(repeat_or_stretch: bool) -> BorderRepeats {
216        BorderRepeats::new_all(repeat_or_stretch)
217    }
218
219    fn from<TB: Into<RepeatMode>, LR: Into<RepeatMode>>((top_bottom, left_right): (TB, LR)) -> BorderRepeats {
220        BorderRepeats::new(top_bottom, left_right)
221    }
222}