Merge 5418cc8e03 into 0d69cd9183
This commit is contained in:
commit
218a689a6c
6 changed files with 211 additions and 141 deletions
|
|
@ -27,7 +27,7 @@ pub enum Button {
|
|||
IconVertical,
|
||||
Image,
|
||||
Link,
|
||||
ListItem,
|
||||
ListItem([f32; 4]),
|
||||
MenuFolder,
|
||||
MenuItem,
|
||||
MenuRoot,
|
||||
|
|
@ -148,9 +148,9 @@ pub fn appearance(
|
|||
appearance.text_color = Some(component.on.into());
|
||||
corner_radii = &cosmic.corner_radii.radius_s;
|
||||
}
|
||||
Button::ListItem => {
|
||||
corner_radii = &[0.0; 4];
|
||||
let (background, text, icon) = color(&cosmic.background.component);
|
||||
Button::ListItem(radii) => {
|
||||
corner_radii = radii;
|
||||
let (background, text, icon) = color(&theme.current_container().component);
|
||||
|
||||
if selected {
|
||||
appearance.background =
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use iced_core::Padding;
|
||||
use iced_widget::container::Catalog;
|
||||
|
||||
use crate::{
|
||||
Apply, Element, theme,
|
||||
widget::{container, divider, space::vertical},
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
||||
ListColumn::default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct ListColumn<'a, Message> {
|
||||
spacing: u16,
|
||||
padding: Padding,
|
||||
list_item_padding: Padding,
|
||||
divider_padding: u16,
|
||||
style: theme::Container<'a>,
|
||||
children: Vec<Element<'a, Message>>,
|
||||
}
|
||||
|
||||
impl<Message: 'static> Default for ListColumn<'_, Message> {
|
||||
fn default() -> Self {
|
||||
let cosmic_theme::Spacing {
|
||||
space_xxs, space_m, ..
|
||||
} = theme::spacing();
|
||||
|
||||
Self {
|
||||
spacing: 0,
|
||||
padding: Padding::from(0),
|
||||
divider_padding: 16,
|
||||
list_item_padding: [space_xxs, space_m].into(),
|
||||
style: theme::Container::List,
|
||||
children: Vec::with_capacity(4),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> ListColumn<'a, Message> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add(self, item: impl Into<Element<'a, Message>>) -> Self {
|
||||
#[inline(never)]
|
||||
fn inner<'a, Message: 'static>(
|
||||
mut this: ListColumn<'a, Message>,
|
||||
item: Element<'a, Message>,
|
||||
) -> ListColumn<'a, Message> {
|
||||
if !this.children.is_empty() {
|
||||
this.children.push(
|
||||
container(divider::horizontal::default())
|
||||
.padding([0, this.divider_padding])
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure a minimum height of 32.
|
||||
let list_item = crate::widget::row![
|
||||
container(item).align_y(iced::Alignment::Center),
|
||||
vertical().height(iced::Length::Fixed(32.))
|
||||
]
|
||||
.padding(this.list_item_padding)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
||||
this.children.push(list_item.into());
|
||||
this
|
||||
}
|
||||
|
||||
inner(self, item.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn spacing(mut self, spacing: u16) -> Self {
|
||||
self.spacing = spacing;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style variant of this [`Circular`].
|
||||
#[inline]
|
||||
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn divider_padding(mut self, padding: u16) -> Self {
|
||||
self.divider_padding = padding;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.list_item_padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_element(self) -> Element<'a, Message> {
|
||||
crate::widget::column::with_children(self.children)
|
||||
.spacing(self.spacing)
|
||||
.padding(self.padding)
|
||||
.width(iced::Length::Fill)
|
||||
.apply(container)
|
||||
.padding([self.spacing, 0])
|
||||
.class(self.style)
|
||||
.width(iced::Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
||||
fn from(column: ListColumn<'a, Message>) -> Self {
|
||||
column.into_element()
|
||||
}
|
||||
}
|
||||
168
src/widget/list/list_column.rs
Normal file
168
src/widget/list/list_column.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::theme::{self, Button, Container};
|
||||
use crate::widget::{button, column, container, row, space::vertical};
|
||||
use crate::{Apply, Element};
|
||||
use iced_core::Padding;
|
||||
use iced_widget::container::Catalog;
|
||||
|
||||
/// A button list item for use in a [`ListColumn`].
|
||||
pub struct ListButton<'a, Message> {
|
||||
content: Element<'a, Message>,
|
||||
on_press: Option<Message>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
/// Creates a [`ListButton`] with the given content.
|
||||
pub fn button<'a, Message>(content: impl Into<Element<'a, Message>>) -> ListButton<'a, Message> {
|
||||
ListButton {
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> ListButton<'a, Message> {
|
||||
pub fn on_press(mut self, on_press: Message) -> Self {
|
||||
self.on_press = Some(on_press);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
|
||||
self.on_press = on_press;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
enum ListItem<'a, Message> {
|
||||
Element(Element<'a, Message>),
|
||||
Button(ListButton<'a, Message>),
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct ListColumn<'a, Message> {
|
||||
list_item_padding: Padding,
|
||||
children: Vec<ListItem<'a, Message>>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
||||
ListColumn::default()
|
||||
}
|
||||
|
||||
pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Message> {
|
||||
let cosmic_theme::Spacing {
|
||||
space_xxs, space_m, ..
|
||||
} = theme::spacing();
|
||||
|
||||
ListColumn {
|
||||
list_item_padding: [space_xxs, space_m].into(),
|
||||
children: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message: 'static> Default for ListColumn<'_, Message> {
|
||||
fn default() -> Self {
|
||||
with_capacity(4)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Adds an element to the list column.
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
|
||||
self.children.push(ListItem::Element(item.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a [`ListButton`] to the list column.
|
||||
pub fn add_button(mut self, item: ListButton<'a, Message>) -> Self {
|
||||
self.children.push(ListItem::Button(item));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.list_item_padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_element(self) -> Element<'a, Message> {
|
||||
let padding = self.list_item_padding;
|
||||
let count = self.children.len();
|
||||
let last_index = count.saturating_sub(1);
|
||||
let radius_s = theme::active().cosmic().radius_s();
|
||||
|
||||
self.children
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, item)| {
|
||||
let is_first = i == 0;
|
||||
let is_last = i == last_index;
|
||||
let radius = get_radius(radius_s, is_first, is_last);
|
||||
|
||||
match item {
|
||||
ListItem::Element(content) => {
|
||||
// Ensure a minimum height of 32
|
||||
row![container(content), vertical().height(32)]
|
||||
.align_y(iced::Alignment::Center)
|
||||
.padding(padding)
|
||||
.width(iced::Length::Fill)
|
||||
.apply(container)
|
||||
.class(Container::custom(move |theme| {
|
||||
let mut style = theme.style(&Container::List);
|
||||
style.border.radius = radius.into();
|
||||
style
|
||||
}))
|
||||
.apply(Element::from)
|
||||
}
|
||||
ListItem::Button(ListButton {
|
||||
content,
|
||||
on_press,
|
||||
selected,
|
||||
}) => {
|
||||
// Ensure a minimum height of 32
|
||||
row![container(content), vertical().height(32)]
|
||||
.align_y(iced::Alignment::Center)
|
||||
.apply(button::custom)
|
||||
.padding(padding)
|
||||
.width(iced::Length::Fill)
|
||||
.on_press_maybe(on_press)
|
||||
.selected(selected)
|
||||
.class(Button::ListItem(radius))
|
||||
.apply(Element::from)
|
||||
}
|
||||
}
|
||||
})
|
||||
.fold(column::with_capacity(count), |col, item| col.push(item))
|
||||
.spacing(2)
|
||||
.width(iced::Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
||||
fn from(column: ListColumn<'a, Message>) -> Self {
|
||||
column.into_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_radius(radius: [f32; 4], first: bool, last: bool) -> [f32; 4] {
|
||||
match (first, last) {
|
||||
(true, true) => radius,
|
||||
(true, false) => [radius[0], radius[1], 2.0, 2.0],
|
||||
(false, true) => [2.0, 2.0, radius[2], radius[3]],
|
||||
(false, false) => [2.0, 2.0, 2.0, 2.0],
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod column;
|
||||
pub mod list_column;
|
||||
|
||||
pub use self::column::{ListColumn, list_column};
|
||||
pub use self::list_column::{ListColumn, button, list_column};
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::Element;
|
||||
use crate::widget::{ListColumn, column, text};
|
||||
use crate::widget::{ListColumn, column, list_column::ListButton, text};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A section within a settings view column.
|
||||
pub fn section<'a, Message: 'static>() -> Section<'a, Message> {
|
||||
pub fn section<'a, Message: Clone + 'static>() -> Section<'a, Message> {
|
||||
with_column(ListColumn::default())
|
||||
}
|
||||
|
||||
/// A section with a pre-defined list column.
|
||||
pub fn with_column<Message: 'static>(children: ListColumn<'_, Message>) -> Section<'_, Message> {
|
||||
pub fn with_column<Message: Clone + 'static>(
|
||||
children: ListColumn<'_, Message>,
|
||||
) -> Section<'_, Message> {
|
||||
Section {
|
||||
header: None,
|
||||
children,
|
||||
|
|
@ -24,9 +26,9 @@ pub struct Section<'a, Message> {
|
|||
children: ListColumn<'a, Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> Section<'a, Message> {
|
||||
impl<'a, Message: Clone + 'static> Section<'a, Message> {
|
||||
/// Define an optional title for the section.
|
||||
pub fn title(mut self, title: impl Into<Cow<'a, str>>) -> Self {
|
||||
pub fn title(self, title: impl Into<Cow<'a, str>>) -> Self {
|
||||
self.header(text::heading(title.into()))
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +54,21 @@ impl<'a, Message: 'static> Section<'a, Message> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add a child [`ListButton`] to the section's list column.
|
||||
pub fn add_button(mut self, item: ListButton<'a, Message>) -> Self {
|
||||
self.children = self.children.add_button(item);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a child [`ListButton`] to the section's list column, if `Some`.
|
||||
pub fn add_button_maybe(self, item: Option<ListButton<'a, Message>>) -> Self {
|
||||
if let Some(item) = item {
|
||||
self.add_button(item)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the [`Section`] with the given children.
|
||||
pub fn extend(
|
||||
self,
|
||||
|
|
@ -61,7 +78,7 @@ impl<'a, Message: 'static> Section<'a, Message> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
||||
impl<'a, Message: Clone + 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
||||
fn from(data: Section<'a, Message>) -> Self {
|
||||
column::with_capacity(2)
|
||||
.spacing(8)
|
||||
|
|
|
|||
|
|
@ -161,7 +161,10 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
|||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
tree::State::new(State {
|
||||
prev_toggled: self.is_toggled,
|
||||
..State::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<Id> {
|
||||
|
|
@ -238,6 +241,14 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
|||
return;
|
||||
};
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
// animate external changes
|
||||
if state.prev_toggled != self.is_toggled {
|
||||
state.anim.changed(self.duration);
|
||||
shell.request_redraw();
|
||||
state.prev_toggled = self.is_toggled;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
|
|
@ -246,6 +257,7 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
|||
if mouse_over {
|
||||
shell.publish((on_toggle)(!self.is_toggled));
|
||||
state.anim.changed(self.duration);
|
||||
state.prev_toggled = !self.is_toggled;
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
|
|
@ -430,4 +442,5 @@ pub fn next_to_each_other(
|
|||
pub struct State {
|
||||
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
||||
anim: anim::State,
|
||||
prev_toggled: bool,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue