zng_wgt_rule_line/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Rule line widgets.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use zng_wgt::{margin, prelude::*};
13use zng_wgt_access::{AccessRole, access_role};
14
15pub mod hr;
16pub mod vr;
17
18mod collapse;
19pub use collapse::*;
20
21/// Draws a horizontal or vertical rule line.
22#[widget($crate::RuleLine)]
23pub struct RuleLine(WidgetBase);
24impl RuleLine {
25    fn widget_intrinsic(&mut self) {
26        self.widget_builder().push_build_action(on_build);
27
28        widget_set! {
29            self;
30            access_role = AccessRole::Separator;
31        }
32    }
33
34    widget_impl! {
35        /// Margin around the line.
36        pub margin(margin: impl IntoVar<SideOffsets>);
37    }
38}
39
40/// Line orientation.
41#[property(CONTEXT, default(LineOrientation::Horizontal), widget_impl(RuleLine))]
42pub fn orientation(wgt: &mut WidgetBuilding, orientation: impl IntoVar<LineOrientation>) {
43    let _ = orientation;
44    wgt.expect_property_capture();
45}
46
47/// Line color.
48#[property(CONTEXT, default(rgb(0, 0, 0)), widget_impl(RuleLine))]
49pub fn color(wgt: &mut WidgetBuilding, color: impl IntoVar<Rgba>) {
50    let _ = color;
51    wgt.expect_property_capture();
52}
53
54/// Line stroke thickness.
55#[property(CONTEXT, default(1), widget_impl(RuleLine))]
56pub fn stroke_thickness(wgt: &mut WidgetBuilding, thickness: impl IntoVar<Length>) {
57    let _ = thickness;
58    wgt.expect_property_capture();
59}
60
61/// Line length.
62///
63/// Set to [`Default`] to fill available length.
64///
65/// [`Default`]: Length::Default
66#[property(CONTEXT, default(Length::Default), widget_impl(RuleLine))]
67pub fn length(wgt: &mut WidgetBuilding, length: impl IntoVar<Length>) {
68    let _ = length;
69    wgt.expect_property_capture();
70}
71
72/// Line style.
73#[property(CONTEXT, default(LineStyle::Solid), widget_impl(RuleLine))]
74pub fn line_style(wgt: &mut WidgetBuilding, style: impl IntoVar<LineStyle>) {
75    let _ = style;
76    wgt.expect_property_capture();
77}
78
79fn on_build(wgt: &mut WidgetBuilding) {
80    let mut bounds = PxSize::zero();
81
82    let orientation = wgt
83        .capture_var(property_id!(orientation))
84        .unwrap_or_else(|| LineOrientation::Horizontal.into_var());
85
86    let length = wgt.capture_var(property_id!(length)).unwrap_or_else(|| const_var(Length::Default));
87
88    let stroke_thickness = wgt
89        .capture_var(property_id!(stroke_thickness))
90        .unwrap_or_else(|| const_var(Length::from(1)));
91
92    let color = wgt.capture_var(property_id!(color)).unwrap_or_else(|| const_var(rgb(0, 0, 0)));
93
94    let style = wgt
95        .capture_var(property_id!(line_style))
96        .unwrap_or_else(|| LineStyle::Solid.into_var());
97
98    wgt.set_child(match_node_leaf(move |op| match op {
99        UiNodeOp::Init => {
100            WIDGET
101                .sub_var_layout(&stroke_thickness)
102                .sub_var_layout(&orientation)
103                .sub_var_layout(&length)
104                .sub_var_render(&color)
105                .sub_var_render(&style);
106        }
107        UiNodeOp::Info { info } => {
108            info.flag_meta(*COLLAPSABLE_LINE_ID);
109        }
110        UiNodeOp::Measure { desired_size, .. } => {
111            if COLLAPSE_SCOPE.collapse(WIDGET.id()) {
112                *desired_size = PxSize::zero();
113                return;
114            }
115
116            let metrics = LAYOUT.metrics();
117            let default_stroke = Dip::new(1).to_px(metrics.scale_factor());
118
119            *desired_size = match orientation.get() {
120                LineOrientation::Horizontal => PxSize::new(
121                    length.layout_dft_x(metrics.constraints().x.fill()),
122                    stroke_thickness.layout_dft_y(default_stroke),
123                ),
124                LineOrientation::Vertical => PxSize::new(
125                    stroke_thickness.layout_dft_x(default_stroke),
126                    length.layout_dft_y(metrics.constraints().y.fill()),
127                ),
128            };
129        }
130        UiNodeOp::Layout { final_size, wl } => {
131            if COLLAPSE_SCOPE.collapse(WIDGET.id()) {
132                wl.collapse();
133                *final_size = PxSize::zero();
134                return;
135            }
136
137            let metrics = LAYOUT.metrics();
138            let default_stroke = Dip::new(1).to_px(metrics.scale_factor());
139
140            let b = match orientation.get() {
141                LineOrientation::Horizontal => PxSize::new(
142                    length.layout_dft_x(metrics.constraints().x.fill()),
143                    stroke_thickness.layout_dft_y(default_stroke),
144                ),
145                LineOrientation::Vertical => PxSize::new(
146                    stroke_thickness.layout_dft_x(default_stroke),
147                    length.layout_dft_y(metrics.constraints().y.fill()),
148                ),
149            };
150
151            if b != bounds {
152                bounds = b;
153                WIDGET.render();
154            }
155
156            *final_size = b;
157        }
158        UiNodeOp::Render { frame } => {
159            if bounds.is_empty() {
160                return;
161            }
162            let bounds = PxRect::from_size(bounds);
163            let orientation = orientation.get();
164            let color = color.get();
165            let style = style.get();
166            frame.push_line(bounds, orientation, color, style);
167        }
168        _ => {}
169    }));
170}