1use crate::{Control, ControlImpl};
2use ferroui_base::collections::FerroList;
3use ferroui_base::input::InputElementImpl;
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::{LayoutableImpl, Orientation};
6use ferroui_base::media::immutable::ImmutablePen;
7use ferroui_base::media::{BrushExtensions, DrawingContext, IBrush, IPen};
8use ferroui_base::utilities::MathUtilities;
9use ferroui_base::{
10 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty,
11 Point, Rect, Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl,
12};
13use std::ops::Deref;
14use std::rc::Rc;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18#[repr(i32)]
19pub enum TickBarPlacement {
20 Left = 0,
22 Top = 1,
24 Right = 2,
26 Bottom = 3,
28}
29
30#[derive(Clone, Default)]
33pub struct TickList(Rc<FerroList<f64>>);
34
35impl TickList {
36 pub fn new() -> Self {
38 Self(Rc::new(FerroList::new()))
39 }
40
41 pub fn from_items(ticks: impl IntoIterator<Item = f64>) -> Self {
43 let result = Self::new();
44 result.add_range(ticks);
45 result
46 }
47
48 #[inline]
50 pub fn list(&self) -> &Rc<FerroList<f64>> {
51 &self.0
52 }
53}
54
55impl Deref for TickList {
56 type Target = FerroList<f64>;
57
58 fn deref(&self) -> &FerroList<f64> {
59 &self.0
60 }
61}
62
63impl PartialEq for TickList {
64 fn eq(&self, other: &Self) -> bool {
65 Rc::ptr_eq(&self.0, &other.0)
66 }
67}
68
69#[repr(C)]
71pub struct TickBar {
72 base: Control,
73}
74
75ferro_class!(TickBar: Control);
76ferroui_base::ferro_class_info!(TickBar { new: TickBar::new });
77ferro_impl_classes!(TickBar: StyledElementImpl, LayoutableImpl, InteractiveImpl, InputElementImpl, ControlImpl);
78
79ferroui_base::ferro_impl_classes!(TickBar: FerroObjectImpl);
80
81impl VisualImpl for TickBar {
82 fn render(this: &Self, dc: &mut DrawingContext) {
96 let bounds = this.bounds();
97 let mut size = Size::new(bounds.width, bounds.height);
98 let minimum = this.minimum();
99 let maximum = this.maximum();
100 let range = maximum - minimum;
101 let tick_len;
103 let mut logical_to_physical;
104 let mut start_point;
105 let mut end_point;
106 let reserved_space = this.reserved_space();
107 let r_space =
108 if this.orientation() == Orientation::Horizontal { reserved_space.width } else { reserved_space.height };
109
110 let half_reserved_space = r_space * 0.5;
112
113 let placement = this.placement();
114
115 match placement {
116 TickBarPlacement::Top => {
117 if MathUtilities::greater_than_or_close(r_space, size.width) {
118 return;
119 }
120 size = Size::new(size.width - r_space, size.height);
121 tick_len = -size.height;
122 start_point = Point::new(half_reserved_space, size.height);
123 end_point = Point::new(half_reserved_space + size.width, size.height);
124 logical_to_physical = size.width / range;
125 }
126 TickBarPlacement::Bottom => {
127 if MathUtilities::greater_than_or_close(r_space, size.width) {
128 return;
129 }
130 size = Size::new(size.width - r_space, size.height);
131 tick_len = size.height;
132 start_point = Point::new(half_reserved_space, 0.0);
133 end_point = Point::new(half_reserved_space + size.width, 0.0);
134 logical_to_physical = size.width / range;
135 }
136 TickBarPlacement::Left => {
137 if MathUtilities::greater_than_or_close(r_space, size.height) {
138 return;
139 }
140 size = Size::new(size.width, size.height - r_space);
141
142 tick_len = -size.width;
143 start_point = Point::new(size.width, size.height + half_reserved_space);
144 end_point = Point::new(size.width, half_reserved_space);
145 logical_to_physical = size.height / range * -1.0;
146 }
147 TickBarPlacement::Right => {
148 if MathUtilities::greater_than_or_close(r_space, size.height) {
149 return;
150 }
151 size = Size::new(size.width, size.height - r_space);
152 tick_len = size.width;
153 start_point = Point::new(0.0, size.height + half_reserved_space);
154 end_point = Point::new(0.0, half_reserved_space);
155 logical_to_physical = size.height / range * -1.0;
156 }
157 }
158
159 let tick_len2 = tick_len * 0.75;
161
162 if this.is_direction_reversed() {
164 logical_to_physical *= -1.0;
165
166 std::mem::swap(&mut start_point, &mut end_point);
168 }
169
170 let pen: Rc<dyn IPen> = Rc::new(ImmutablePen::with_brush(
171 this.fill().map(|fill| BrushExtensions::to_immutable(&fill)),
172 1.0,
173 ));
174
175 if placement == TickBarPlacement::Left || placement == TickBarPlacement::Right {
177 let mut interval = this.tick_frequency();
180 if interval > 0.0 {
181 let min_interval = (maximum - minimum) / size.height;
182 if interval < min_interval {
183 interval = min_interval;
184 }
185 }
186
187 dc.draw_line(&pen, start_point, Point::new(start_point.x + tick_len, start_point.y));
189 dc.draw_line(
190 &pen,
191 Point::new(start_point.x, end_point.y),
192 Point::new(start_point.x + tick_len, end_point.y),
193 );
194
195 let ticks = this.ticks().filter(|ticks| ticks.count() > 0);
196
197 if let Some(ticks) = ticks {
199 for &tick in ticks.snapshot().iter() {
200 if MathUtilities::less_than_or_close(tick, minimum)
201 || MathUtilities::greater_than_or_close(tick, maximum)
202 {
203 continue;
204 }
205
206 let adjusted_tick = tick - minimum;
207
208 let y = adjusted_tick * logical_to_physical + start_point.y;
209 dc.draw_line(&pen, Point::new(start_point.x, y), Point::new(start_point.x + tick_len2, y));
210 }
211 }
212 else if interval > 0.0 {
214 let mut i = interval;
215 while i < range {
216 let y = i * logical_to_physical + start_point.y;
217
218 dc.draw_line(&pen, Point::new(start_point.x, y), Point::new(start_point.x + tick_len2, y));
219 i += interval;
220 }
221 }
222 } else {
223 let mut interval = this.tick_frequency();
228 if interval > 0.0 {
229 let min_interval = (maximum - minimum) / size.width;
230 if interval < min_interval {
231 interval = min_interval;
232 }
233 }
234
235 dc.draw_line(&pen, start_point, Point::new(start_point.x, start_point.y + tick_len));
237 dc.draw_line(
238 &pen,
239 Point::new(end_point.x, start_point.y),
240 Point::new(end_point.x, start_point.y + tick_len),
241 );
242
243 let ticks = this.ticks().filter(|ticks| ticks.count() > 0);
244
245 if let Some(ticks) = ticks {
247 for &tick in ticks.snapshot().iter() {
248 if MathUtilities::less_than_or_close(tick, minimum)
249 || MathUtilities::greater_than_or_close(tick, maximum)
250 {
251 continue;
252 }
253 let adjusted_tick = tick - minimum;
254
255 let x = adjusted_tick * logical_to_physical + start_point.x;
256 dc.draw_line(&pen, Point::new(x, start_point.y), Point::new(x, start_point.y + tick_len2));
257 }
258 }
259 else if interval > 0.0 {
261 let mut i = interval;
262 while i < range {
263 let x = i * logical_to_physical + start_point.x;
264 dc.draw_line(&pen, Point::new(x, start_point.y), Point::new(x, start_point.y + tick_len2));
265 i += interval;
266 }
267 }
268 }
269 }
270}
271
272ferroui_base::ferro_properties! { impl TickBar {
273 ferro_property!(
274 pub fn fill_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
276 FerroProperty::register::<TickBar, _>("Fill", None)
277 }
278 );
279
280 ferro_property!(
281 pub fn minimum_property() -> StyledProperty<f64> {
283 FerroProperty::register::<TickBar, _>("Minimum", 0.0)
284 }
285 );
286
287 ferro_property!(
288 pub fn maximum_property() -> StyledProperty<f64> {
290 FerroProperty::register::<TickBar, _>("Maximum", 0.0)
291 }
292 );
293
294 ferro_property!(
295 pub fn tick_frequency_property() -> StyledProperty<f64> {
297 FerroProperty::register::<TickBar, _>("TickFrequency", 0.0)
298 }
299 );
300
301 ferro_property!(
302 pub fn orientation_property() -> StyledProperty<Orientation> {
304 FerroProperty::register::<TickBar, _>("Orientation", Orientation::Horizontal)
305 }
306 );
307
308 ferro_property!(
309 pub fn ticks_property() -> StyledProperty<Option<TickList>> {
311 FerroProperty::register::<TickBar, _>("Ticks", None)
312 }
313 );
314
315 ferro_property!(
316 pub fn is_direction_reversed_property() -> StyledProperty<bool> {
318 FerroProperty::register::<TickBar, _>("IsDirectionReversed", false)
319 }
320 );
321
322 ferro_property!(
323 pub fn placement_property() -> StyledProperty<TickBarPlacement> {
325 FerroProperty::register::<TickBar, _>("Placement", TickBarPlacement::Left)
326 }
327 );
328
329 ferro_property!(
330 pub fn reserved_space_property() -> StyledProperty<Rect> {
332 FerroProperty::register::<TickBar, _>("ReservedSpace", Rect::default())
333 }
334 );
335} }
336
337impl TickBar {
338 fn static_constructor() {
339 Visual::affects_render::<TickBar>(&[
340 Self::fill_property().as_property(),
341 Self::is_direction_reversed_property().as_property(),
342 Self::reserved_space_property().as_property(),
343 Self::maximum_property().as_property(),
344 Self::minimum_property().as_property(),
345 Self::orientation_property().as_property(),
346 Self::placement_property().as_property(),
347 Self::tick_frequency_property().as_property(),
348 Self::ticks_property().as_property(),
349 ]);
350 }
351
352 pub fn construct() -> Self {
354 Self { base: Control::construct() }
355 }
356
357 pub fn new() -> Ref<Self> {
358 instantiate(Self::construct())
359 }
360
361 pub fn fill(&self) -> Option<Rc<dyn IBrush>> {
363 self.get_value(Self::fill_property())
364 }
365
366 pub fn set_fill(&self, value: Option<Rc<dyn IBrush>>) {
367 self.set_value(Self::fill_property(), value)
368 }
369
370 pub fn minimum(&self) -> f64 {
372 self.get_value(Self::minimum_property())
373 }
374
375 pub fn set_minimum(&self, value: f64) {
376 self.set_value(Self::minimum_property(), value)
377 }
378
379 pub fn maximum(&self) -> f64 {
381 self.get_value(Self::maximum_property())
382 }
383
384 pub fn set_maximum(&self, value: f64) {
385 self.set_value(Self::maximum_property(), value)
386 }
387
388 pub fn tick_frequency(&self) -> f64 {
390 self.get_value(Self::tick_frequency_property())
391 }
392
393 pub fn set_tick_frequency(&self, value: f64) {
394 self.set_value(Self::tick_frequency_property(), value)
395 }
396
397 pub fn orientation(&self) -> Orientation {
399 self.get_value(Self::orientation_property())
400 }
401
402 pub fn set_orientation(&self, value: Orientation) {
403 self.set_value(Self::orientation_property(), value)
404 }
405
406 pub fn ticks(&self) -> Option<TickList> {
409 self.get_value(Self::ticks_property())
410 }
411
412 pub fn set_ticks(&self, value: Option<TickList>) {
413 self.set_value(Self::ticks_property(), value)
414 }
415
416 pub fn is_direction_reversed(&self) -> bool {
423 self.get_value(Self::is_direction_reversed_property())
424 }
425
426 pub fn set_is_direction_reversed(&self, value: bool) {
427 self.set_value(Self::is_direction_reversed_property(), value)
428 }
429
430 pub fn placement(&self) -> TickBarPlacement {
433 self.get_value(Self::placement_property())
434 }
435
436 pub fn set_placement(&self, value: TickBarPlacement) {
437 self.set_value(Self::placement_property(), value)
438 }
439
440 pub fn reserved_space(&self) -> Rect {
445 self.get_value(Self::reserved_space_property())
446 }
447
448 pub fn set_reserved_space(&self, value: Rect) {
449 self.set_value(Self::reserved_space_property(), value)
450 }
451}