1use crate::debug_state::DebugState;
16
17use crate::widget::Axis;
18use druid::widget::prelude::*;
19use druid::Data;
20use tracing::{instrument, warn};
21
22pub struct AspectRatioBox<T> {
30 child: Box<dyn Widget<T>>,
31 ratio: f64,
32}
33
34impl<T> AspectRatioBox<T> {
35 pub fn new(child: impl Widget<T> + 'static, ratio: f64) -> Self {
41 Self {
42 child: Box::new(child),
43 ratio: clamp_ratio(ratio),
44 }
45 }
46
47 pub fn set_ratio(&mut self, ratio: f64) {
53 self.ratio = clamp_ratio(ratio);
54 }
55
56 fn generate_constraints(&self, bc: &BoxConstraints) -> BoxConstraints {
61 let (mut new_width, mut new_height) = (bc.max().width, bc.max().height);
62
63 if new_width == f64::INFINITY {
64 new_width = new_height * self.ratio;
65 } else {
66 new_height = new_width / self.ratio;
67 }
68
69 if new_width > bc.max().width {
70 new_width = bc.max().width;
71 new_height = new_width / self.ratio;
72 }
73
74 if new_height > bc.max().height {
75 new_height = bc.max().height;
76 new_width = new_height * self.ratio;
77 }
78
79 if new_width < bc.min().width {
80 new_width = bc.min().width;
81 new_height = new_width / self.ratio;
82 }
83
84 if new_height < bc.min().height {
85 new_height = bc.min().height;
86 new_width = new_height * self.ratio;
87 }
88
89 BoxConstraints::tight(bc.constrain(Size::new(new_width, new_height)))
90 }
91}
92
93fn clamp_ratio(mut ratio: f64) -> f64 {
96 ratio = f64::clamp(ratio, 0.0, f64::MAX);
97
98 if ratio == 0.0 {
99 warn!("Provided ratio was <= 0.0.");
100 1.0
101 } else {
102 ratio
103 }
104}
105
106impl<T: Data> Widget<T> for AspectRatioBox<T> {
107 #[instrument(
108 name = "AspectRatioBox",
109 level = "trace",
110 skip(self, ctx, event, data, env)
111 )]
112 fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
113 self.child.event(ctx, event, data, env);
114 }
115
116 #[instrument(
117 name = "AspectRatioBox",
118 level = "trace",
119 skip(self, ctx, event, data, env)
120 )]
121 fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
122 self.child.lifecycle(ctx, event, data, env)
123 }
124
125 #[instrument(
126 name = "AspectRatioBox",
127 level = "trace",
128 skip(self, ctx, old_data, data, env)
129 )]
130 fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
131 self.child.update(ctx, old_data, data, env);
132 }
133
134 #[instrument(
135 name = "AspectRatioBox",
136 level = "trace",
137 skip(self, ctx, bc, data, env)
138 )]
139 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
140 bc.debug_check("AspectRatioBox");
141
142 if bc.max() == bc.min() {
143 warn!("Box constraints are tight. Aspect ratio box will not be able to preserve aspect ratio.");
144
145 return self.child.layout(ctx, bc, data, env);
146 }
147 if bc.max().width == f64::INFINITY && bc.max().height == f64::INFINITY {
148 warn!("Box constraints are INFINITE. Aspect ratio box won't be able to choose a size because the constraints given by the parent widget are INFINITE.");
149
150 return self.child.layout(ctx, bc, data, env);
151 }
152
153 let bc = self.generate_constraints(bc);
154
155 self.child.layout(ctx, &bc, data, env)
156 }
157
158 #[instrument(name = "AspectRatioBox", level = "trace", skip(self, ctx, data, env))]
159 fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
160 self.child.paint(ctx, data, env);
161 }
162
163 fn id(&self) -> Option<WidgetId> {
164 self.child.id()
165 }
166
167 fn debug_state(&self, data: &T) -> DebugState {
168 DebugState {
169 display_name: self.short_type_name().to_string(),
170 children: vec![self.child.debug_state(data)],
171 ..Default::default()
172 }
173 }
174
175 fn compute_max_intrinsic(
176 &mut self,
177 axis: Axis,
178 ctx: &mut LayoutCtx,
179 bc: &BoxConstraints,
180 data: &T,
181 env: &Env,
182 ) -> f64 {
183 match axis {
184 Axis::Horizontal => {
185 if bc.is_height_bounded() {
186 bc.max().height * self.ratio
187 } else {
188 self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
189 }
190 }
191 Axis::Vertical => {
192 if bc.is_width_bounded() {
193 bc.max().width / self.ratio
194 } else {
195 self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
196 }
197 }
198 }
199 }
200}