1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0

//! A control for incremental adjustments of a value.

mod model;
use std::borrow::Cow;

pub use self::model::{Message, Model};

use crate::widget::{button, container, icon, row, text};
use crate::{theme, Element};
use apply::Apply;
use iced::{Alignment, Length};
use iced_core::{Border, Shadow};

pub struct SpinButton<'a, Message> {
    label: Cow<'a, str>,
    on_change: Box<dyn Fn(model::Message) -> Message + 'static>,
}

/// A control for incremental adjustments of a value.
pub fn spin_button<'a, Message: 'static>(
    label: impl Into<Cow<'a, str>>,
    on_change: impl Fn(model::Message) -> Message + 'static,
) -> SpinButton<'a, Message> {
    SpinButton::new(label, on_change)
}

impl<'a, Message: 'static> SpinButton<'a, Message> {
    pub fn new(
        label: impl Into<Cow<'a, str>>,
        on_change: impl Fn(model::Message) -> Message + 'static,
    ) -> Self {
        Self {
            on_change: Box::from(on_change),
            label: label.into(),
        }
    }

    #[must_use]
    pub fn into_element(self) -> Element<'a, Message> {
        let Self { on_change, label } = self;
        container(
            row::with_children(vec![
                icon::from_name("list-remove-symbolic")
                    .size(16)
                    .apply(container)
                    .center(Length::Fixed(32.0))
                    .apply(button::custom)
                    .width(Length::Fixed(32.0))
                    .height(Length::Fixed(32.0))
                    .class(theme::Button::Text)
                    .on_press(model::Message::Decrement)
                    .into(),
                text::title4(label)
                    .apply(container)
                    .center_x(Length::Fixed(48.0))
                    .align_y(Alignment::Center)
                    .into(),
                icon::from_name("list-add-symbolic")
                    .size(16)
                    .apply(container)
                    .center(Length::Fixed(32.0))
                    .apply(button::custom)
                    .width(Length::Fixed(32.0))
                    .height(Length::Fixed(32.0))
                    .class(theme::Button::Text)
                    .on_press(model::Message::Increment)
                    .into(),
            ])
            .width(Length::Shrink)
            .height(Length::Fixed(32.0))
            .align_y(Alignment::Center),
        )
        .width(Length::Shrink)
        .center_y(Length::Fixed(32.0))
        .class(theme::Container::custom(container_style))
        .apply(Element::from)
        .map(on_change)
    }
}

impl<'a, Message: 'static> From<SpinButton<'a, Message>> for Element<'a, Message> {
    fn from(spin_button: SpinButton<'a, Message>) -> Self {
        spin_button.into_element()
    }
}

#[allow(clippy::trivially_copy_pass_by_ref)]
fn container_style(theme: &crate::Theme) -> iced_widget::container::Style {
    let basic = &theme.cosmic();
    let mut neutral_10 = basic.palette.neutral_10;
    neutral_10.alpha = 0.1;
    let accent = &basic.accent;
    let corners = &basic.corner_radii;
    iced_widget::container::Style {
        icon_color: Some(basic.palette.neutral_10.into()),
        text_color: Some(basic.palette.neutral_10.into()),
        background: None,
        border: Border {
            radius: corners.radius_s.into(),
            width: 0.0,
            color: accent.base.into(),
        },
        shadow: Shadow::default(),
    }
}