Implemented Expander
- Updated example to show behavior - Created styles for Expander and ExpanderRow - Simpler implementation of `ExpanderRow` - Deleted `ExpanderData` and replaced it with `ExpanderRow` - Every row can now have child rows. - Ran cargo fmt. - Deleted settings example - Added expander to cosmic example - Expander icons now render ListBox partially implemented
This commit is contained in:
parent
a50294676d
commit
7743d0d084
22 changed files with 1222 additions and 738 deletions
|
|
@ -1,81 +1,232 @@
|
|||
use std::vec;
|
||||
|
||||
use crate::list_box_row;
|
||||
use apply::Apply;
|
||||
use derive_setters::Setters;
|
||||
use iced::{
|
||||
Element,
|
||||
Length,
|
||||
widget::{
|
||||
row,
|
||||
horizontal_space, button, container, text, Column
|
||||
}, alignment::{Vertical, Horizontal}, theme
|
||||
theme,
|
||||
widget::{self, button, container, horizontal_space, row, text, Column},
|
||||
Alignment, Background, Element, Length, Renderer, Theme,
|
||||
};
|
||||
use iced_native::widget::column;
|
||||
use iced_lazy::Component;
|
||||
use iced_native::widget::{column, event_container, horizontal_rule};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Expander
|
||||
{
|
||||
pub expanded: bool
|
||||
#[derive(Setters)]
|
||||
pub struct Expander<'a, Message> {
|
||||
title: &'a str,
|
||||
#[setters(strip_option)]
|
||||
subtitle: Option<&'a str>,
|
||||
#[setters(strip_option)]
|
||||
icon: Option<String>,
|
||||
expansible: bool,
|
||||
#[setters(skip)]
|
||||
rows: Option<Vec<ExpanderRow<'a>>>,
|
||||
#[setters(strip_option)]
|
||||
on_row_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>,
|
||||
}
|
||||
|
||||
pub fn expander<'a, Message>() -> Expander<'a, Message> {
|
||||
Expander {
|
||||
title: "",
|
||||
subtitle: None,
|
||||
icon: None,
|
||||
expansible: false,
|
||||
rows: None,
|
||||
on_row_selected: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Setters, Default, Debug, Clone)]
|
||||
pub struct ExpanderRow<'a> {
|
||||
pub(crate) title: &'a str,
|
||||
#[setters(strip_option)]
|
||||
pub subtitle: Option<&'a str>,
|
||||
#[setters(strip_option)]
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
pub fn expander_row<'a>() -> ExpanderRow<'a> {
|
||||
ExpanderRow {
|
||||
title: "",
|
||||
subtitle: None,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpanderState {
|
||||
pub expanded: bool,
|
||||
}
|
||||
|
||||
impl Default for ExpanderState {
|
||||
fn default() -> Self {
|
||||
Self { expanded: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ExpanderMsg {
|
||||
pub enum ExpanderEvent {
|
||||
Expand,
|
||||
RowSelected(usize),
|
||||
}
|
||||
|
||||
impl Expander {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
impl<'a, Message> Expander<'a, Message> {
|
||||
pub fn rows(mut self, rows: Vec<ExpanderRow<'a>>) -> Self {
|
||||
self.rows = Some(rows);
|
||||
self.expansible = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render<'a, T>(&self, children: Vec<Element<'a, T>>) -> Element<'a, T>
|
||||
where T: Clone + From<ExpanderMsg> + 'static
|
||||
{
|
||||
let title = text("Title")
|
||||
.size(18)
|
||||
.vertical_alignment(Vertical::Center)
|
||||
.horizontal_alignment(Horizontal::Center)
|
||||
.into();
|
||||
let subtitle = iced::widget::text("Subtitle")
|
||||
.size(14)
|
||||
.vertical_alignment(Vertical::Center)
|
||||
.horizontal_alignment(Horizontal::Center)
|
||||
.into();
|
||||
let header = column(
|
||||
vec![title, subtitle]
|
||||
).into();
|
||||
let space = horizontal_space(Length::Fill).into();
|
||||
let icon = super::icon(
|
||||
if self.expanded {
|
||||
"go-down-symbolic"
|
||||
} else {
|
||||
"go-next-symbolic"
|
||||
},
|
||||
16
|
||||
)
|
||||
.apply(button)
|
||||
.on_press(T::from(ExpanderMsg::Expand))
|
||||
.width(Length::Units(25))
|
||||
.into();
|
||||
|
||||
container(
|
||||
column(
|
||||
if self.expanded {
|
||||
vec![
|
||||
row(vec![header, space, icon]).into(),
|
||||
container(
|
||||
Column::with_children(children)
|
||||
)
|
||||
.style(theme::Container::Transparent)
|
||||
.padding(5)
|
||||
.into()
|
||||
]
|
||||
} else {
|
||||
vec![row(vec![header, space, icon]).into()]
|
||||
}
|
||||
)
|
||||
.padding(5)
|
||||
)
|
||||
.style(theme::Container::Box)
|
||||
.into()
|
||||
pub fn push(&mut self, row: ExpanderRow<'a>) {
|
||||
if self.rows.is_none() {
|
||||
self.rows = Some(vec![])
|
||||
}
|
||||
self.rows.as_mut().unwrap().push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'a> Component<Message, Renderer> for Expander<'a, Message> {
|
||||
type State = ExpanderState;
|
||||
|
||||
type Event = ExpanderEvent;
|
||||
|
||||
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
|
||||
match event {
|
||||
ExpanderEvent::Expand => {
|
||||
state.expanded = !state.expanded;
|
||||
None
|
||||
}
|
||||
ExpanderEvent::RowSelected(index) => self
|
||||
.on_row_selected
|
||||
.as_ref()
|
||||
.map(|on_row_selected| (on_row_selected)(index)),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, state: &Self::State) -> Element<Self::Event> {
|
||||
let heading: Element<ExpanderEvent, Renderer> = {
|
||||
let mut captions = vec![text(&self.title).size(18).into()];
|
||||
if let Some(subtitle) = &self.subtitle {
|
||||
captions.push(text(subtitle).size(16).into());
|
||||
}
|
||||
let text = column(captions);
|
||||
let space: Element<ExpanderEvent, Renderer> = horizontal_space(Length::Fill).into();
|
||||
let toggler: Element<ExpanderEvent, Renderer> = {
|
||||
let mut icon = super::icon(
|
||||
if state.expanded {
|
||||
"go-down-symbolic"
|
||||
} else {
|
||||
"go-next-symbolic"
|
||||
},
|
||||
16,
|
||||
)
|
||||
.apply(button)
|
||||
.width(Length::Units(25));
|
||||
if self.expansible {
|
||||
icon = icon.on_press(ExpanderEvent::Expand);
|
||||
}
|
||||
icon.into()
|
||||
};
|
||||
|
||||
let items = if let Some(icon) = &self.icon {
|
||||
let icon = super::icon(icon.as_str(), 20)
|
||||
.apply(event_container)
|
||||
.padding(10);
|
||||
row![icon, text, space, toggler]
|
||||
} else {
|
||||
row![text, space, toggler]
|
||||
};
|
||||
|
||||
container(items.align_items(Alignment::Center))
|
||||
.style(theme::Container::Custom(expander_heading_style))
|
||||
.padding(10)
|
||||
.into()
|
||||
};
|
||||
|
||||
let rows: Vec<Element<_>> = if let Some(rows) = &self.rows {
|
||||
rows.iter()
|
||||
.enumerate()
|
||||
.map(|(index, row)| {
|
||||
let subtitle = row.subtitle.unwrap_or_default();
|
||||
if let Some(icon) = &row.icon {
|
||||
list_box_row!(row.title, subtitle, icon.as_str())
|
||||
.apply(event_container)
|
||||
.on_press(ExpanderEvent::RowSelected(index))
|
||||
.into()
|
||||
} else {
|
||||
list_box_row!(row.title, subtitle)
|
||||
.apply(event_container)
|
||||
.on_press(ExpanderEvent::RowSelected(index))
|
||||
.into()
|
||||
}
|
||||
})
|
||||
.enumerate()
|
||||
.flat_map(|(index, child)| {
|
||||
if index != rows.len() - 1 {
|
||||
vec![
|
||||
child,
|
||||
horizontal_rule(1)
|
||||
.style(theme::Rule::Custom(separator_style))
|
||||
.into(),
|
||||
]
|
||||
} else {
|
||||
vec![child]
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let rows: Element<ExpanderEvent> = Column::with_children(rows).into();
|
||||
|
||||
let mut layout = vec![heading];
|
||||
if state.expanded && self.expansible {
|
||||
layout.push(rows)
|
||||
}
|
||||
|
||||
column(layout)
|
||||
.apply(widget::container)
|
||||
.height(Length::Shrink)
|
||||
.style(theme::Container::Custom(expander_row_style))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'a> From<Expander<'a, Message>> for Element<'a, Message> {
|
||||
fn from(expander: Expander<'a, Message>) -> Self {
|
||||
iced_lazy::component(expander)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expander_heading_style(theme: &Theme) -> widget::container::Appearance {
|
||||
let primary = &theme.cosmic().primary;
|
||||
let accent = &theme.cosmic().accent;
|
||||
widget::container::Appearance {
|
||||
text_color: Some(accent.base.into()),
|
||||
background: Some(Background::Color(primary.divider.into())),
|
||||
border_radius: 8.0,
|
||||
border_width: 0.0,
|
||||
border_color: primary.on.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expander_row_style(theme: &Theme) -> widget::container::Appearance {
|
||||
let cosmic = &theme.cosmic().primary;
|
||||
widget::container::Appearance {
|
||||
text_color: Some(cosmic.on.into()),
|
||||
background: Some(Background::Color(cosmic.base.into())),
|
||||
border_radius: 8.0,
|
||||
border_width: 0.4,
|
||||
border_color: cosmic.divider.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separator_style(theme: &Theme) -> widget::rule::Appearance {
|
||||
let cosmic = &theme.cosmic().primary;
|
||||
widget::rule::Appearance {
|
||||
color: cosmic.divider.into(),
|
||||
width: 1,
|
||||
radius: 0.0,
|
||||
fill_mode: widget::rule::FillMode::Padded(10),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue