Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-widgets/src/top_bar.rs
StratoSDK / crates / strato-widgets / src / top_bar.rs
1use crate::{
2 container::Container,
3 layout::{CrossAxisAlignment, MainAxisAlignment, Row},
4 text::{FontWeight, Text},
5 widget::{generate_id, WidgetContext, WidgetId},
6 Widget,
7};
8use std::any::Any;
9use strato_core::{
10 event::{Event, EventResult},
11 layout::{Constraints, Layout, Size},
12 types::{Color, Point},
13};
14 
15/// A standardized top bar / header widget
16#[derive(Debug)]
17pub struct TopBar {
18 id: WidgetId,
19 inner: Option<Box<dyn Widget>>,
20 
21 // Props
22 pub title: String,
23 pub leading: Option<Box<dyn Widget>>,
24 pub trailing: Option<Box<dyn Widget>>,
25 pub height: f32,
26 pub background: Color,
27}
28 
29impl TopBar {
30 pub fn new(title: String) -> Self {
31 Self {
32 id: generate_id(),
33 inner: None,
34 title,
35 leading: None,
36 trailing: None,
37 height: 48.0,
38 background: Color::rgba(0.1, 0.1, 0.1, 0.95), // Default dark nice background
39 }
40 }
41 
42 pub fn with_background(mut self, color: Color) -> Self {
43 self.background = color;
44 self.inner = None; // Invalidate inner widget
45 self
46 }
47 
48 pub fn with_leading(mut self, widget: impl Widget + 'static) -> Self {
49 self.leading = Some(Box::new(widget));
50 self.inner = None;
51 self
52 }
53 
54 pub fn with_trailing(mut self, widget: impl Widget + 'static) -> Self {
55 self.trailing = Some(Box::new(widget));
56 self.inner = None; // Invalidate inner widget
57 self
58 }
59 
60 pub fn height(mut self, height: f32) -> Self {
61 self.height = height;
62 self.inner = None;
63 self
64 }
65 
66 fn ensure_inner(&mut self) {
67 if self.inner.is_some() {
68 return;
69 }
70 
71 // Build the inner widget tree
72 let title_widget = Text::new(&self.title)
73 .size(16.0)
74 .font_weight(FontWeight::SemiBold)
75 .color(Color::WHITE);
76 
77 let mut row = Row::new()
78 .main_axis_alignment(MainAxisAlignment::Center)
79 .cross_axis_alignment(CrossAxisAlignment::Center)
80 .spacing(16.0);
81 
82 if let Some(leading) = &self.leading {
83 row = row.child(leading.clone_widget());
84 }
85 
86 row = row.child(Box::new(title_widget) as Box<dyn Widget>);
87 
88 if let Some(trailing) = &self.trailing {
89 row = row.child(trailing.clone_widget());
90 }
91 
92 let container = Container::new()
93 .width(f32::MAX) // Full width
94 .height(self.height)
95 .background(self.background)
96 .padding(12.0)
97 .child(row);
98 
99 self.inner = Some(Box::new(container));
100 }
101}
102 
103impl Widget for TopBar {
104 fn id(&self) -> WidgetId {
105 self.id
106 }
107 
108 fn layout(&mut self, constraints: Constraints) -> Size {
109 self.ensure_inner();
110 self.inner.as_mut().unwrap().layout(constraints)
111 }
112 
113 fn render(&self, batch: &mut strato_renderer::batch::RenderBatch, layout: Layout) {
114 if let Some(inner) = &self.inner {
115 inner.render(batch, layout);
116 }
117 }
118 
119 fn handle_event(&mut self, event: &Event) -> EventResult {
120 self.ensure_inner();
121 self.inner.as_mut().unwrap().handle_event(event)
122 }
123 
124 fn update(&mut self, ctx: &WidgetContext) {
125 if let Some(inner) = &mut self.inner {
126 inner.update(ctx);
127 }
128 }
129 
130 fn children(&self) -> Vec<&(dyn Widget + '_)> {
131 if let Some(inner) = &self.inner {
132 vec![inner.as_ref()]
133 } else {
134 vec![]
135 }
136 }
137 
138 fn children_mut(&mut self) -> Vec<&mut (dyn Widget + '_)> {
139 if let Some(inner) = &mut self.inner {
140 vec![inner.as_mut()]
141 } else {
142 vec![]
143 }
144 }
145 
146 fn hit_test(&self, point: Point, layout: Layout) -> bool {
147 if let Some(inner) = &self.inner {
148 inner.hit_test(point, layout)
149 } else {
150 false
151 }
152 }
153 
154 fn as_any(&self) -> &dyn Any {
155 self
156 }
157 fn as_any_mut(&mut self) -> &mut dyn Any {
158 self
159 }
160 
161 fn clone_widget(&self) -> Box<dyn Widget> {
162 Box::new(Self {
163 id: generate_id(), // New ID
164 inner: None, // Reset inner to force rebuild/fresh state
165 title: self.title.clone(),
166 leading: self.leading.as_ref().map(|w| w.clone_widget()),
167 trailing: self.trailing.as_ref().map(|w| w.clone_widget()),
168 height: self.height,
169 background: self.background,
170 })
171 }
172}
173