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