use ribir_core::prelude::*;
use crate::prelude::*;
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum ButtonLabelVisibility {
#[default]
Show,
Hide,
}
#[derive(Default, Declare)]
pub struct Button;
#[derive(Declare, Default)]
pub struct FilledButton;
#[derive(Default, Declare)]
pub struct TextButton;
#[derive(Default, Declare)]
pub struct Fab;
#[derive(Debug, Clone, Copy)]
pub enum FabSize {
Mini,
Normal,
Large,
}
#[derive(Template)]
pub struct ButtonChild<'c> {
label: Option<TextValue>,
icon: Option<PositionChild<Widget<'c>>>,
}
class_names! {
#[doc = "This class specifies a fully basic button, including both an icon and a label."]
BUTTON,
#[doc="This class specifies for the label of the basic button."]
BTN_LABEL,
#[doc="This class specifies for the leading icon of the basic button."]
BTN_LEADING_ICON,
#[doc="This class specifies for the trailing icon of the basic button."]
BTN_TRAILING_ICON,
#[doc="This class specifies for the icon-only basic button."]
BTN_ICON_ONLY,
#[doc="This class specifies for the label-only basic button."]
BTN_LABEL_ONLY,
}
impl<'c> ComposeChild<'c> for Button {
type Child = ButtonChild<'c>;
fn compose_child(_: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
child.compose_to_widget([
BUTTON,
BTN_LEADING_ICON,
BTN_TRAILING_ICON,
BTN_LABEL,
BTN_ICON_ONLY,
BTN_LABEL_ONLY,
])
}
}
class_names! {
#[doc = "This class specifies a fully text button, including both an icon and a label."]
TEXT_BTN,
#[doc = "This class specifies for the label of the text button."]
TEXT_BTN_LABEL,
#[doc = "This class specifies for the leading icon of the text button."]
TEXT_BTN_LEADING_ICON,
#[doc = "This class specifies for the trailing icon of the text button."]
TEXT_BTN_TRAILING_ICON,
#[doc = "This class specifies for the icon-only text button."]
TEXT_BTN_ICON_ONLY,
#[doc = "This class specifies for the label-only text button."]
TEXT_BTN_LABEL_ONLY,
}
impl<'c> ComposeChild<'c> for TextButton {
type Child = ButtonChild<'c>;
fn compose_child(_: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
child.compose_to_widget([
TEXT_BTN,
TEXT_BTN_LEADING_ICON,
TEXT_BTN_TRAILING_ICON,
TEXT_BTN_LABEL,
TEXT_BTN_ICON_ONLY,
TEXT_BTN_LABEL_ONLY,
])
}
}
class_names! {
#[doc = "This class specifies a fully filled button, including both an icon and a label."]
FILLED_BTN,
#[doc = "This class specifies for the label of the filled button."]
FILLED_BTN_LABEL,
#[doc = "This class specifies for the leading icon of the filled button."]
FILLED_BTN_LEADING_ICON,
#[doc = "This class specifies for the trailing icon of the filled button."]
FILLED_BTN_TRAILING_ICON,
#[doc = "This class specifies for the icon-only filled button."]
FILLED_BTN_ICON_ONLY,
#[doc = "This class specifies for the label-only filled button."]
FILLED_BTN_LABEL_ONLY,
}
impl<'c> ComposeChild<'c> for FilledButton {
type Child = ButtonChild<'c>;
fn compose_child(_: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
child.compose_to_widget([
FILLED_BTN,
FILLED_BTN_LEADING_ICON,
FILLED_BTN_TRAILING_ICON,
FILLED_BTN_LABEL,
FILLED_BTN_ICON_ONLY,
FILLED_BTN_LABEL_ONLY,
])
}
}
class_names! {
#[doc = "This class specifies a fully fab button, including both an icon and a label."]
FAB,
#[doc = "This class specifies for the label of the fab button."]
FAB_LABEL,
#[doc = "This class specifies for the leading icon of the fab button."]
FAB_LEADING_ICON,
#[doc = "This class specifies for the trailing icon of the fab button."]
FAB_TRAILING_ICON,
#[doc = "This class specifies for the icon-only fab button."]
FAB_ICON_ONLY,
#[doc = "This class specifies for the label-only fab button."]
FAB_LABEL_ONLY
}
impl<'c> ComposeChild<'c> for Fab {
type Child = ButtonChild<'c>;
fn compose_child(_: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
child.compose_to_widget([
FAB,
FAB_LEADING_ICON,
FAB_TRAILING_ICON,
FAB_LABEL,
FAB_ICON_ONLY,
FAB_LABEL_ONLY,
])
}
}
impl<'c> ButtonChild<'c> {
fn compose_to_widget(
self,
[btn, btn_leading_icon, btn_trialing_icon, btn_label, icon_only, label_only]: [ClassName; 6],
) -> Widget<'c> {
let Self { label, icon } = self;
match (label, icon) {
(None, None) => void!(class: btn).into_widget(),
(None, Some(icon)) => fat_obj! {
class: icon_only,
@ { icon.unwrap() }
}
.into_widget(),
(Some(text), None) => text! { class: label_only, text }.into_widget(),
(Some(text), Some(icon)) => {
let trailing_icon = icon.is_trailing();
let label_visible = Variant::<ButtonLabelVisibility>::new_or_default(BuildCtx::get())
.map(|v| *v == ButtonLabelVisibility::Show);
let icon_widget = class! {
class: if trailing_icon { btn_trialing_icon } else { btn_leading_icon },
@ { icon.unwrap() }
};
let label_widget = text! {
class: btn_label,
text,
visible: $clone(label_visible),
};
let (first, second) = if trailing_icon {
(label_widget.into_widget(), icon_widget.into_widget())
} else {
(icon_widget.into_widget(), label_widget.into_widget())
};
row! {
class: label_visible.map(move |show| if show { btn } else { icon_only }),
align_items: Align::Center,
justify_content: JustifyContent::Center,
@ { first }
@ { second }
}
.into_widget()
}
}
}
}
#[cfg(test)]
mod tests {
use ribir_core::test_helper::*;
use ribir_dev_helper::*;
use super::*;
fn miss_icon() -> Svg { svg_registry::get_or_default("default") }
widget_image_tests!(
button,
WidgetTester::new(flex! {
justify_content: JustifyContent::SpaceAround,
line_gap: 20.,
wrap: true,
@TextButton { @Icon { @miss_icon() } }
@TextButton { @{ "Label only"} }
@TextButton {
@Icon { @miss_icon() }
@ { "Default icon position" }
}
@TextButton {
@Leading::new(@Icon { @miss_icon() })
@ { "Leading icon" }
}
@TextButton {
@ { "Trailing icon" }
@Trailing::new(@Icon { @miss_icon() })
}
})
.with_wnd_size(Size::new(400., 120.))
.with_comparison(0.00003)
);
widget_image_tests!(
filled_button,
WidgetTester::new(flex! {
justify_content: JustifyContent::SpaceAround,
y: AnchorY::center(),
line_gap: 20.,
wrap: true,
@FilledButton { @Icon { @miss_icon() } }
@FilledButton { @{ "Label only"} }
@FilledButton {
@Icon { @miss_icon() }
@ { "Default icon position" }
}
@FilledButton {
@Leading::new(@Icon { @miss_icon() })
@ { "Leading icon" }
}
@FilledButton {
@ { "Trailing icon" }
@Trailing::new(@Icon { @miss_icon() })
}
})
.with_wnd_size(Size::new(400., 128.))
.with_comparison(0.00004),
);
widget_image_tests!(
outlined_button,
WidgetTester::new(flex! {
justify_content: JustifyContent::SpaceAround,
y: AnchorY::center(),
line_gap: 20.,
wrap: true,
@Button { @Icon { @miss_icon() } }
@Button { @{ "Label only"} }
@Button {
@Icon { @miss_icon() }
@ { "Default icon position" }
}
@Button {
@Leading::new(@Icon { @miss_icon() })
@ { "Leading icon" }
}
@Button {
@ { "Trailing icon" }
@Trailing::new(@Icon { @miss_icon() })
}
})
.with_wnd_size(Size::new(400., 128.))
.with_comparison(0.0001),
);
widget_image_tests!(
mini_fab,
WidgetTester::new(flex! {
justify_content: JustifyContent::SpaceAround,
y: AnchorY::center(),
line_gap: 20.,
wrap: true,
@Fab {
providers: [Provider::new(FabSize::Mini)],
@Icon { @miss_icon() }
}
@Fab {
providers: [Provider::new(FabSize::Mini)],
@{ "Label only"}
}
@Fab {
providers: [Provider::new(FabSize::Mini)],
@Icon { @miss_icon() }
@ { "Default icon position"
}
}
@Fab {
providers: [Provider::new(FabSize::Mini)],
@Leading::new(@Icon { @miss_icon() })
@ { "Leading icon" }
}
@Fab {
providers: [Provider::new(FabSize::Mini)],
@ { "Trailing icon" }
@Trailing::new(@Icon { @miss_icon() })
}
})
.with_wnd_size(Size::new(400., 128.))
.with_comparison(0.00005)
);
widget_image_tests!(
fab,
WidgetTester::new(flex! {
justify_content: JustifyContent::SpaceAround,
y: AnchorY::center(),
line_gap: 20.,
wrap: true,
@Fab { @Icon { @miss_icon() } }
@Fab { @{ "Label only"} }
@Fab {
@Icon { @miss_icon() }
@ { "Default icon position" }
}
@Fab {
@Leading::new(@Icon { @miss_icon() })
@ { "Leading icon" }
}
@Fab {
@ { "Trailing icon" }
@Trailing::new(@Icon { @miss_icon() })
}
})
.with_wnd_size(Size::new(400., 164.)),
);
widget_image_tests!(
large_fab,
WidgetTester::new(flex! {
justify_content: JustifyContent::SpaceAround,
y: AnchorY::center(),
line_gap: 20.,
wrap: true,
@Fab {
providers: [Provider::new(FabSize::Large)],
@{ "Label only"}
}
@Fab {
providers: [Provider::new(FabSize::Large)],
@Icon { @miss_icon() }
@ { "Default icon position" }
}
@Fab {
providers: [Provider::new(FabSize::Large)],
@Leading::new(@Icon { @miss_icon() })
@ { "Leading icon" }
}
@Fab {
providers: [Provider::new(FabSize::Large)],
@Icon { @miss_icon() }
}
@Fab {
providers: [Provider::new(FabSize::Large)],
@ { "Trailing icon" }
@Trailing::new(@Icon { @miss_icon() })
}
})
.with_wnd_size(Size::new(640., 256.)),
);
widget_image_tests!(
button_label_visibility,
WidgetTester::new(self::column! {
@Row {
providers: [Provider::new(ButtonLabelVisibility::Show)],
@button! { @Icon { @miss_icon() } @ { "Show" } }
@filled_button! { @Icon { @miss_icon() } @ { "Show" } }
@text_button! { @Icon { @miss_icon() } @ { "Show" } }
@fab! { @Icon { @miss_icon() } @ { "Show" } }
}
@Row {
providers: [Provider::new(ButtonLabelVisibility::Hide)],
@button! { @Icon { @miss_icon() } @ { "Hide" } }
@filled_button! { @Icon { @miss_icon() } @ { "Hide" } }
@text_button! { @Icon { @miss_icon() } @ { "Hide" } }
@fab! { @Icon { @miss_icon() } @ { "Hide" } }
}
})
.with_wnd_size(Size::new(400., 128.)),
);
}