1use crate::cosmic_theme::{Density, Spacing};
5use crate::{Element, theme, widget};
6use apply::Apply;
7use derive_setters::Setters;
8use iced::Length;
9use iced_core::{Vector, Widget, widget::tree};
10use std::borrow::Cow;
11
12#[must_use]
13pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
14 HeaderBar {
15 title: Cow::Borrowed(""),
16 on_close: None,
17 on_drag: None,
18 on_maximize: None,
19 on_minimize: None,
20 on_right_click: None,
21 start: Vec::new(),
22 center: Vec::new(),
23 end: Vec::new(),
24 density: None,
25 focused: false,
26 maximized: false,
27 on_double_click: None,
28 }
29}
30
31#[derive(Setters)]
32pub struct HeaderBar<'a, Message> {
33 #[setters(skip)]
35 title: Cow<'a, str>,
36
37 #[setters(strip_option)]
39 on_close: Option<Message>,
40
41 #[setters(strip_option)]
43 on_drag: Option<Message>,
44
45 #[setters(strip_option)]
47 on_maximize: Option<Message>,
48
49 #[setters(strip_option)]
51 on_minimize: Option<Message>,
52
53 #[setters(strip_option)]
56 on_double_click: Option<Message>,
57
58 #[setters(strip_option)]
60 on_right_click: Option<Message>,
61
62 #[setters(skip)]
64 start: Vec<Element<'a, Message>>,
65
66 #[setters(skip)]
68 center: Vec<Element<'a, Message>>,
69
70 #[setters(skip)]
72 end: Vec<Element<'a, Message>>,
73
74 #[setters(strip_option)]
76 density: Option<Density>,
77
78 focused: bool,
80
81 maximized: bool,
83}
84
85impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
86 #[must_use]
88 pub fn title(mut self, title: impl Into<Cow<'a, str>> + 'a) -> Self {
89 self.title = title.into();
90 self
91 }
92
93 #[must_use]
95 pub fn start(mut self, widget: impl Into<Element<'a, Message>> + 'a) -> Self {
96 self.start.push(widget.into());
97 self
98 }
99
100 #[must_use]
102 pub fn center(mut self, widget: impl Into<Element<'a, Message>> + 'a) -> Self {
103 self.center.push(widget.into());
104 self
105 }
106
107 #[must_use]
109 pub fn end(mut self, widget: impl Into<Element<'a, Message>> + 'a) -> Self {
110 self.end.push(widget.into());
111 self
112 }
113
114 #[must_use]
116 #[inline]
117 pub fn build(self) -> HeaderBarWidget<'a, Message> {
118 HeaderBarWidget {
119 header_bar_inner: self.view(),
120 }
121 }
122}
123
124pub struct HeaderBarWidget<'a, Message> {
125 header_bar_inner: Element<'a, Message>,
126}
127
128impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
129 for HeaderBarWidget<'_, Message>
130{
131 fn diff(&mut self, tree: &mut tree::Tree) {
132 tree.diff_children(&mut [&mut self.header_bar_inner]);
133 }
134
135 fn children(&self) -> Vec<tree::Tree> {
136 vec![tree::Tree::new(&self.header_bar_inner)]
137 }
138
139 fn size(&self) -> iced_core::Size<Length> {
140 self.header_bar_inner.as_widget().size()
141 }
142
143 fn layout(
144 &self,
145 tree: &mut tree::Tree,
146 renderer: &crate::Renderer,
147 limits: &iced_core::layout::Limits,
148 ) -> iced_core::layout::Node {
149 let child_tree = &mut tree.children[0];
150 let child = self
151 .header_bar_inner
152 .as_widget()
153 .layout(child_tree, renderer, limits);
154 iced_core::layout::Node::with_children(child.size(), vec![child])
155 }
156
157 fn draw(
158 &self,
159 tree: &tree::Tree,
160 renderer: &mut crate::Renderer,
161 theme: &crate::Theme,
162 style: &iced_core::renderer::Style,
163 layout: iced_core::Layout<'_>,
164 cursor: iced_core::mouse::Cursor,
165 viewport: &iced_core::Rectangle,
166 ) {
167 let layout_children = layout.children().next().unwrap();
168 let state_children = &tree.children[0];
169 self.header_bar_inner.as_widget().draw(
170 state_children,
171 renderer,
172 theme,
173 style,
174 layout_children,
175 cursor,
176 viewport,
177 );
178 }
179
180 fn on_event(
181 &mut self,
182 state: &mut tree::Tree,
183 event: iced_core::Event,
184 layout: iced_core::Layout<'_>,
185 cursor: iced_core::mouse::Cursor,
186 renderer: &crate::Renderer,
187 clipboard: &mut dyn iced_core::Clipboard,
188 shell: &mut iced_core::Shell<'_, Message>,
189 viewport: &iced_core::Rectangle,
190 ) -> iced_core::event::Status {
191 let child_state = &mut state.children[0];
192 let child_layout = layout.children().next().unwrap();
193 self.header_bar_inner.as_widget_mut().on_event(
194 child_state,
195 event,
196 child_layout,
197 cursor,
198 renderer,
199 clipboard,
200 shell,
201 viewport,
202 )
203 }
204
205 fn mouse_interaction(
206 &self,
207 state: &tree::Tree,
208 layout: iced_core::Layout<'_>,
209 cursor: iced_core::mouse::Cursor,
210 viewport: &iced_core::Rectangle,
211 renderer: &crate::Renderer,
212 ) -> iced_core::mouse::Interaction {
213 let child_tree = &state.children[0];
214 let child_layout = layout.children().next().unwrap();
215 self.header_bar_inner.as_widget().mouse_interaction(
216 child_tree,
217 child_layout,
218 cursor,
219 viewport,
220 renderer,
221 )
222 }
223
224 fn operate(
225 &self,
226 state: &mut tree::Tree,
227 layout: iced_core::Layout<'_>,
228 renderer: &crate::Renderer,
229 operation: &mut dyn iced_core::widget::Operation<()>,
230 ) {
231 let child_tree = &mut state.children[0];
232 let child_layout = layout.children().next().unwrap();
233 self.header_bar_inner
234 .as_widget()
235 .operate(child_tree, child_layout, renderer, operation);
236 }
237
238 fn overlay<'b>(
239 &'b mut self,
240 state: &'b mut tree::Tree,
241 layout: iced_core::Layout<'_>,
242 renderer: &crate::Renderer,
243 translation: Vector,
244 ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
245 let child_tree = &mut state.children[0];
246 let child_layout = layout.children().next().unwrap();
247 self.header_bar_inner.as_widget_mut().overlay(
248 child_tree,
249 child_layout,
250 renderer,
251 translation,
252 )
253 }
254
255 fn drag_destinations(
256 &self,
257 state: &tree::Tree,
258 layout: iced_core::Layout<'_>,
259 renderer: &crate::Renderer,
260 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
261 ) {
262 if let Some((child_tree, child_layout)) =
263 state.children.iter().zip(layout.children()).next()
264 {
265 self.header_bar_inner.as_widget().drag_destinations(
266 child_tree,
267 child_layout,
268 renderer,
269 dnd_rectangles,
270 );
271 }
272 }
273
274 #[cfg(feature = "a11y")]
275 fn a11y_nodes(
277 &self,
278 layout: iced_core::Layout<'_>,
279 state: &tree::Tree,
280 p: iced::mouse::Cursor,
281 ) -> iced_accessibility::A11yTree {
282 let c_layout = layout.children().next().unwrap();
283 let c_state = &state.children[0];
284 let ret = self
285 .header_bar_inner
286 .as_widget()
287 .a11y_nodes(c_layout, c_state, p);
288 ret
289 }
290}
291
292impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
293 pub fn view(mut self) -> Element<'a, Message> {
295 let Spacing {
296 space_xxxs,
297 space_xxs,
298 ..
299 } = theme::spacing();
300
301 let start = std::mem::take(&mut self.start);
303 let center = std::mem::take(&mut self.center);
304 let mut end = std::mem::take(&mut self.end);
305
306 end.push(self.window_controls());
308
309 let padding = match self.density.unwrap_or_else(crate::config::header_size) {
311 Density::Compact => {
312 if self.maximized {
313 [4, 8, 4, 8]
314 } else {
315 [3, 7, 4, 7]
316 }
317 }
318 _ => {
319 if self.maximized {
320 [8, 8, 8, 8]
321 } else {
322 [7, 7, 8, 7]
323 }
324 }
325 };
326 let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round()
327 as u16)
328 .max(1);
329 let center_empty = center.is_empty() && self.title.is_empty();
330 let mut widget = widget::row::with_capacity(3)
332 .push(
334 widget::row::with_children(start)
335 .spacing(space_xxxs)
336 .align_y(iced::Alignment::Center)
337 .apply(widget::container)
338 .align_x(iced::Alignment::Start)
339 .width(Length::FillPortion(portion)),
340 )
341 .push(if !center.is_empty() {
344 widget::row::with_children(center)
345 .spacing(space_xxxs)
346 .align_y(iced::Alignment::Center)
347 .apply(widget::container)
348 .center_x(Length::Fill)
349 .into()
350 } else if self.title.is_empty() {
351 widget::horizontal_space().into()
352 } else {
353 self.title_widget()
354 })
355 .push(
356 widget::row::with_children(end)
357 .spacing(space_xxs)
358 .align_y(iced::Alignment::Center)
359 .apply(widget::container)
360 .align_x(iced::Alignment::End)
361 .width(if center_empty {
362 Length::Fill
363 } else {
364 Length::FillPortion(portion)
365 }),
366 )
367 .align_y(iced::Alignment::Center)
368 .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
369 .padding(padding)
370 .spacing(8)
371 .apply(widget::container)
372 .class(crate::theme::Container::HeaderBar {
373 focused: self.focused,
374 })
375 .center_y(Length::Shrink)
376 .apply(widget::mouse_area);
377
378 if let Some(message) = self.on_drag.clone() {
380 widget = widget.on_drag(message);
381 }
382
383 if let Some(message) = self.on_maximize.clone() {
385 widget = widget.on_release(message);
386 }
387 if let Some(message) = self.on_double_click.clone() {
388 widget = widget.on_double_press(message);
389 }
390 if let Some(message) = self.on_right_click.clone() {
391 widget = widget.on_right_press(message);
392 }
393
394 widget.into()
395 }
396
397 fn title_widget(&mut self) -> Element<'a, Message> {
398 let mut title = Cow::default();
399 std::mem::swap(&mut title, &mut self.title);
400
401 widget::text::heading(title)
402 .apply(widget::container)
403 .center(Length::Fill)
404 .into()
405 }
406
407 fn window_controls(&mut self) -> Element<'a, Message> {
409 macro_rules! icon {
410 ($name:expr, $size:expr, $on_press:expr) => {{
411 #[cfg(target_os = "linux")]
412 let icon = {
413 widget::icon::from_name($name)
414 .apply(widget::button::icon)
415 .padding(8)
416 };
417
418 #[cfg(not(target_os = "linux"))]
419 let icon = {
420 widget::icon::from_svg_bytes(include_bytes!(concat!(
421 "../../res/icons/",
422 $name,
423 ".svg"
424 )))
425 .symbolic(true)
426 .apply(widget::button::icon)
427 .padding(8)
428 };
429
430 icon.class(crate::theme::Button::HeaderBar)
431 .selected(self.focused)
432 .icon_size($size)
433 .on_press($on_press)
434 }};
435 }
436
437 widget::row::with_capacity(3)
438 .push_maybe(
439 self.on_minimize
440 .take()
441 .map(|m: Message| icon!("window-minimize-symbolic", 16, m)),
442 )
443 .push_maybe(self.on_maximize.take().map(|m| {
444 if self.maximized {
445 icon!("window-restore-symbolic", 16, m)
446 } else {
447 icon!("window-maximize-symbolic", 16, m)
448 }
449 }))
450 .push_maybe(
451 self.on_close
452 .take()
453 .map(|m| icon!("window-close-symbolic", 16, m)),
454 )
455 .spacing(theme::spacing().space_xxs)
456 .apply(widget::container)
457 .center_y(Length::Fill)
458 .into()
459 }
460}
461
462impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
463 fn from(headerbar: HeaderBar<'a, Message>) -> Self {
464 Element::new(headerbar.build())
465 }
466}
467
468impl<'a, Message: Clone + 'static> From<HeaderBarWidget<'a, Message>> for Element<'a, Message> {
469 fn from(headerbar: HeaderBarWidget<'a, Message>) -> Self {
470 Element::new(headerbar)
471 }
472}