cosmic/widget/settings/
item.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use std::borrow::Cow;
5
6use crate::{
7    Element, Theme, theme,
8    widget::{FlexRow, Row, column, container, flex_row, row, text},
9};
10use derive_setters::Setters;
11use iced_core::{Length, text::Wrapping};
12use iced_widget::space;
13use taffy::AlignContent;
14
15/// A settings item aligned in a row
16#[must_use]
17#[allow(clippy::module_name_repetitions)]
18pub fn item<'a, Message: 'static>(
19    title: impl Into<Cow<'a, str>> + 'a,
20    widget: impl Into<Element<'a, Message>> + 'a,
21) -> Row<'a, Message, Theme> {
22    #[inline(never)]
23    fn inner<'a, Message: 'static>(
24        title: Cow<'a, str>,
25        widget: Element<'a, Message>,
26    ) -> Row<'a, Message, Theme> {
27        item_row(vec![
28            text(title).wrapping(Wrapping::Word).into(),
29            space::horizontal().into(),
30            widget,
31        ])
32    }
33
34    inner(title.into(), widget.into())
35}
36
37/// A settings item aligned in a row
38#[must_use]
39#[allow(clippy::module_name_repetitions)]
40pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message, Theme> {
41    row::with_children(children)
42        .spacing(theme::spacing().space_xs)
43        .align_y(iced::Alignment::Center)
44        .width(Length::Fill)
45}
46
47/// A settings item aligned in a flex row
48#[allow(clippy::module_name_repetitions)]
49pub fn flex_item<'a, Message: 'static>(
50    title: impl Into<Cow<'a, str>> + 'a,
51    widget: impl Into<Element<'a, Message>> + 'a,
52) -> FlexRow<'a, Message> {
53    #[inline(never)]
54    fn inner<'a, Message: 'static>(
55        title: Cow<'a, str>,
56        widget: Element<'a, Message>,
57    ) -> FlexRow<'a, Message> {
58        flex_item_row(vec![
59            text(title)
60                .wrapping(Wrapping::Word)
61                .width(Length::Fill)
62                .into(),
63            container(widget).width(Length::Shrink).into(),
64        ])
65        .width(Length::Fill)
66    }
67
68    inner(title.into(), widget.into())
69}
70
71/// A settings item aligned in a flex row
72#[allow(clippy::module_name_repetitions)]
73pub fn flex_item_row<Message>(children: Vec<Element<Message>>) -> FlexRow<Message> {
74    flex_row(children)
75        .spacing(theme::spacing().space_xs)
76        .min_item_width(200.0)
77        .justify_items(iced::Alignment::Center)
78        .justify_content(AlignContent::SpaceBetween)
79        .width(Length::Fill)
80}
81
82/// Creates a builder for an item, beginning with the title.
83pub fn builder<'a, Message: 'static>(title: impl Into<Cow<'a, str>>) -> Item<'a, Message> {
84    Item {
85        title: title.into(),
86        description: None,
87        icon: None,
88    }
89}
90
91/// A builder for a settings item.
92#[derive(Setters)]
93pub struct Item<'a, Message> {
94    /// Describes the item being controlled.
95    title: Cow<'a, str>,
96
97    /// A description to display beneath the title.
98    #[setters(strip_option, into)]
99    description: Option<Cow<'a, str>>,
100
101    /// A custom icon to display before the text.
102    #[setters(strip_option, into)]
103    icon: Option<Element<'a, Message>>,
104}
105
106impl<'a, Message: 'static> Item<'a, Message> {
107    /// Assigns a control to the item.
108    pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message, Theme> {
109        item_row(self.control_(widget.into()))
110    }
111
112    /// Assigns a control which flexes.
113    pub fn flex_control(self, widget: impl Into<Element<'a, Message>>) -> FlexRow<'a, Message> {
114        flex_item_row(self.control_(widget.into()))
115    }
116
117    #[inline(never)]
118    fn control_(self, widget: Element<'a, Message>) -> Vec<Element<'a, Message>> {
119        let mut contents = Vec::with_capacity(4);
120
121        if let Some(icon) = self.icon {
122            contents.push(icon);
123        }
124
125        if let Some(description) = self.description {
126            let column = column::with_capacity(2)
127                .spacing(2)
128                .push(text::body(self.title).wrapping(Wrapping::Word))
129                .push(text::caption(description).wrapping(Wrapping::Word))
130                .width(Length::Fill);
131
132            contents.push(column.into());
133        } else {
134            contents.push(text(self.title).width(Length::Fill).into());
135        }
136
137        contents.push(widget);
138        contents
139    }
140
141    pub fn toggler(
142        self,
143        is_checked: bool,
144        message: impl Fn(bool) -> Message + 'static,
145    ) -> Row<'a, Message, Theme> {
146        self.control(
147            crate::widget::toggler(is_checked)
148                .width(Length::Shrink)
149                .on_toggle(message),
150        )
151    }
152}