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