use crate::view::{View, Widget, WidgetKind};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Validity {
Valid,
WrongType,
NotPresent,
}
macro_rules! component {
($(#[$doc:meta])* $name:ident, matches = $matches:expr) => {
$(#[$doc])*
pub struct $name {
widget: Widget,
validity: Validity,
}
impl $name {
pub fn from_widget(widget: Widget) -> $name {
let matches: fn(WidgetKind) -> bool = $matches;
let validity = if !widget.is_valid() {
Validity::NotPresent
} else if matches(widget.kind()) {
Validity::Valid
} else {
Validity::WrongType
};
$name { widget, validity }
}
pub(crate) fn query(view: &View, name: &str) -> $name {
Self::from_widget(view.find_widget(name))
}
pub fn is_valid(&self) -> bool {
self.validity == Validity::Valid && self.widget.is_valid()
}
pub fn validity(&self) -> Validity {
self.validity
}
pub fn attached(&self) -> bool {
self.widget.is_valid()
}
pub fn id(&self) -> String {
self.widget.name()
}
pub fn kind(&self) -> WidgetKind {
self.widget.kind()
}
pub fn attr(&self, name: &str, fallback: &str) -> String {
self.widget.attr(name, fallback)
}
pub fn set_attr(&self, name: &str, value: &str) -> &Self {
self.widget.set_attr(name, value);
self
}
pub fn text(&self) -> String {
self.widget.text()
}
pub fn set_text(&self, value: &str) -> &Self {
self.widget.set_text(value);
self
}
pub fn visible(&self) -> bool {
!self.widget.has_attr("hidden")
}
pub fn set_visible(&self, on: bool) -> &Self {
if on {
self.widget.remove_attr("hidden");
} else {
self.widget.set_attr("hidden", "");
}
self
}
pub fn widget(&self) -> &Widget {
&self.widget
}
}
};
}
component!(
Button,
matches = |k| k == WidgetKind::Button
);
impl Button {
pub fn label(&self) -> String {
if self.is_valid() { self.widget.text() } else { String::new() }
}
pub fn set_label(&self, label: &str) -> &Self {
if self.is_valid() {
self.widget.set_text(label);
}
self
}
pub fn enabled(&self) -> bool {
self.is_valid() && !self.widget.has_attr("disabled")
}
pub fn set_enabled(&self, on: bool) -> &Self {
if self.is_valid() {
if on {
self.widget.remove_attr("disabled");
} else {
self.widget.set_attr("disabled", "");
}
}
self
}
pub fn on_click(&self, f: impl FnMut() + 'static) -> &Self {
if self.is_valid() {
self.widget.on_click(f);
}
self
}
}
component!(
Checkbox,
matches = |k| k == WidgetKind::Checkbox
);
impl Checkbox {
pub fn checked(&self) -> bool {
self.is_valid() && self.widget.attr("aria-checked", "") == "true"
}
pub fn set_checked(&self, on: bool) -> &Self {
if self.is_valid() {
self.widget.set_attr("aria-checked", if on { "true" } else { "false" });
}
self
}
pub fn on_change(&self, f: impl FnMut(&str) + 'static) -> &Self {
if self.is_valid() {
self.widget.on_change(f);
}
self
}
}
component!(
TextField,
matches = |k| k == WidgetKind::TextInput
);
impl TextField {
pub fn value(&self) -> String {
if self.is_valid() { self.widget.attr("value", "") } else { String::new() }
}
pub fn set_value(&self, value: &str) -> &Self {
if self.is_valid() {
self.widget.set_attr("value", value);
}
self
}
pub fn on_change(&self, f: impl FnMut(&str) + 'static) -> &Self {
if self.is_valid() {
self.widget.on_change(f);
}
self
}
}
component!(
Dropdown,
matches = |k| k == WidgetKind::Dropdown
);
impl Dropdown {
pub fn selected(&self) -> String {
if self.is_valid() { self.widget.attr("data-value", "") } else { String::new() }
}
pub fn set_selected(&self, value: &str) -> &Self {
if self.is_valid() {
self.widget.set_attr("data-value", value);
}
self
}
pub fn on_change(&self, f: impl FnMut(&str) + 'static) -> &Self {
if self.is_valid() {
self.widget.on_change(f);
}
self
}
}
component!(
Slider,
matches = |k| k == WidgetKind::Slider
);
impl Slider {
pub fn value(&self, fallback: f64) -> f64 {
if !self.is_valid() {
return fallback;
}
let mut text = self.widget.attr("aria-valuenow", "");
if text.is_empty() {
text = self.widget.attr("data-value", "");
}
text.parse().unwrap_or(fallback)
}
pub fn on_change(&self, f: impl FnMut(&str) + 'static) -> &Self {
if self.is_valid() {
self.widget.on_change(f);
}
self
}
}
component!(
ColorField,
matches = |k| k == WidgetKind::TextInput || k == WidgetKind::Container
);
impl ColorField {
pub fn color(&self) -> String {
if !self.is_valid() {
return String::new();
}
let v = self.widget.attr("data-value", "");
if v.is_empty() { self.widget.attr("value", "") } else { v }
}
pub fn set_color(&self, css_color: &str) -> &Self {
if self.is_valid() {
self.widget.set_attr("data-value", css_color);
}
self
}
pub fn on_change(&self, f: impl FnMut(&str) + 'static) -> &Self {
if self.is_valid() {
self.widget.on_change(f);
}
self
}
}
component!(
DockPanel,
matches = |k| {
k == WidgetKind::Container || k == WidgetKind::Panel || k == WidgetKind::Card
}
);
impl DockPanel {
pub fn active_tab(&self) -> String {
if self.is_valid() { self.widget.attr("data-active-tab", "") } else { String::new() }
}
pub fn set_active_tab(&self, tab_id: &str) -> &Self {
if self.is_valid() {
self.widget.set_attr("data-active-tab", tab_id);
}
self
}
}
component!(
Foldout,
matches = |k| k == WidgetKind::Container
);
impl Foldout {
pub fn open(&self) -> bool {
self.is_valid() && self.widget.attr("aria-expanded", "") != "false"
}
pub fn set_open(&self, on: bool) -> &Self {
if self.is_valid() {
self.widget.set_attr("aria-expanded", if on { "true" } else { "false" });
}
self
}
}
impl View {
pub fn button_at(&self, name: &str) -> Button {
Button::query(self, name)
}
pub fn checkbox_at(&self, name: &str) -> Checkbox {
Checkbox::query(self, name)
}
pub fn text_field_at(&self, name: &str) -> TextField {
TextField::query(self, name)
}
pub fn dropdown_at(&self, name: &str) -> Dropdown {
Dropdown::query(self, name)
}
pub fn slider_at(&self, name: &str) -> Slider {
Slider::query(self, name)
}
pub fn color_field_at(&self, name: &str) -> ColorField {
ColorField::query(self, name)
}
pub fn dock_panel_at(&self, name: &str) -> DockPanel {
DockPanel::query(self, name)
}
pub fn foldout_at(&self, name: &str) -> Foldout {
Foldout::query(self, name)
}
}