use iced::widget::text::LineHeight;
use iced::widget::{checkbox, container};
use iced::{Element, Font, Pixels, Theme, widget};
use serde_json::Value;
use crate::PlushieRenderer;
use crate::a11y::A11yOverrides;
use crate::iced_convert;
use crate::message::Message;
use crate::protocol::TreeNode;
use crate::registry::PlushieWidget;
use crate::render_ctx::RenderCtx;
use crate::widget::helpers::*;
use plushie_core::types::{
A11y, Font as PlushieFont, Length, LineHeight as PlushieLineHeight, PlushieType, Shaping,
Style as CoreStyle, Wrapping,
};
struct CheckboxProps {
label: Option<String>,
checked: bool,
disabled: bool,
spacing: Option<f32>,
size: Option<f32>,
width: Option<Length>,
font: Option<PlushieFont>,
text_size: Option<f32>,
line_height: Option<PlushieLineHeight>,
shaping: Option<Shaping>,
wrapping: Option<Wrapping>,
style: Option<CoreStyle>,
}
impl CheckboxProps {
fn from_node(node: &TreeNode) -> Self {
let p = &node.props;
Self {
label: String::extract(p, "label"),
checked: prop_bool_default(p, "checked", false),
disabled: prop_bool_default(p, "disabled", false),
spacing: f32::extract(p, "spacing"),
size: f32::extract(p, "size"),
width: Length::extract(p, "width"),
font: PlushieFont::extract(p, "font"),
text_size: f32::extract(p, "text_size"),
line_height: PlushieLineHeight::extract(p, "line_height"),
shaping: Shaping::extract(p, "shaping"),
wrapping: Wrapping::extract(p, "wrapping"),
style: CoreStyle::extract(p, "style"),
}
}
}
pub(crate) struct CheckboxWidget;
impl<R: PlushieRenderer> PlushieWidget<R> for CheckboxWidget {
fn type_names(&self) -> &[&str] {
&["checkbox"]
}
fn render<'a>(
&'a self,
node: &'a TreeNode,
ctx: &RenderCtx<'a, R>,
) -> Element<'a, Message, Theme, R> {
render_checkbox(node, *ctx)
}
fn infer_a11y(&self, node: &TreeNode) -> Option<A11yOverrides> {
let mut a11y = A11y::new();
let mut any = false;
if let Some(c) = node
.props
.get_str("mnemonic")
.or_else(|| node.props.get_str("access_key"))
.and_then(|s| s.chars().next())
{
a11y = a11y.mnemonic(c);
any = true;
}
if prop_bool_default(&node.props, "disabled", false) {
a11y = a11y.disabled(true);
any = true;
}
if any {
Some(A11yOverrides::from_core(&a11y))
} else {
None
}
}
fn fresh_for_session(&self) -> Box<dyn PlushieWidget<R>> {
Box::new(CheckboxWidget)
}
}
fn render_checkbox<'a, R: PlushieRenderer>(
node: &'a TreeNode,
ctx: RenderCtx<'a, R>,
) -> Element<'a, Message, Theme, R> {
let cp = CheckboxProps::from_node(node);
let id = node.id.clone();
let label = cp.label.unwrap_or_default();
let width = cp
.width
.as_ref()
.map(iced_convert::length)
.unwrap_or(iced::Length::Shrink);
let mut cb = checkbox(cp.checked).label(label).width(width);
if !cp.disabled {
cb = cb.on_toggle(move |v| Message::Event {
window_id: ctx.window_id.to_string(),
id: id.clone(),
value: Value::Bool(v),
family: "toggle".into(),
});
}
if let Some(s) = cp.spacing {
cb = cb.spacing(s);
}
if let Some(sz) = cp.size {
cb = cb.size(sz);
}
if let Some(ts) = cp.text_size.or(ctx.default_text_size) {
cb = cb.text_size(ts);
}
let font = cp.font.map(|f| iced_convert::font(&f)).or(ctx.default_font);
if let Some(f) = font {
cb = cb.font(f);
}
if let Some(lh) = cp.line_height {
cb = cb.line_height(iced_convert::line_height(lh));
}
if let Some(s) = cp.shaping {
cb = cb.shaping(iced_convert::shaping(s));
}
if let Some(w) = cp.wrapping {
cb = cb.wrapping(iced_convert::wrapping(w));
}
let icon_prop = node.props.get_value("icon");
if let Some(icon_val) = icon_prop.as_ref().and_then(|v| v.as_object())
&& let Some(cp_str) = icon_val.get("code_point").and_then(|v| v.as_str())
&& let Some(code_point) = cp_str.chars().next()
{
let icon_font = icon_val
.get("font")
.and_then(plushie_core::types::Font::wire_decode)
.map(|f| iced_convert::font(&f))
.unwrap_or(Font::DEFAULT);
let icon_size = icon_val
.get("size")
.and_then(|v| v.as_f64())
.map(|v| Pixels(v as f32));
let icon_line_height = icon_val
.get("line_height")
.and_then(|v| match v {
Value::Number(n) => n.as_f64().map(|r| LineHeight::Relative(r as f32)),
Value::Object(obj) => {
if let Some(r) = obj.get("relative").and_then(|v| v.as_f64()) {
Some(LineHeight::Relative(r as f32))
} else {
obj.get("absolute")
.and_then(|v| v.as_f64())
.map(|a| LineHeight::Absolute(Pixels(a as f32)))
}
}
_ => None,
})
.unwrap_or(LineHeight::default());
let icon_shaping = icon_val
.get("shaping")
.and_then(|v| v.as_str())
.and_then(|s| match s.to_ascii_lowercase().as_str() {
"basic" => Some(iced::widget::text::Shaping::Basic),
"advanced" => Some(iced::widget::text::Shaping::Advanced),
"auto" => Some(iced::widget::text::Shaping::Auto),
_ => None,
})
.unwrap_or(iced::widget::text::Shaping::Auto);
let icon_struct = checkbox::Icon {
font: icon_font,
code_point,
size: icon_size,
line_height: icon_line_height,
shaping: icon_shaping,
};
cb = cb.icon(icon_struct);
}
match &cp.style {
Some(CoreStyle::Preset(name)) => {
cb = match name.as_str() {
"primary" => cb.style(checkbox::primary),
"secondary" => cb.style(checkbox::secondary),
"success" => cb.style(checkbox::success),
"danger" => cb.style(checkbox::danger),
_ => {
log::warn!(
"unknown style {:?} for widget type {:?}, using default",
name,
"checkbox"
);
cb.style(checkbox::primary)
}
};
}
Some(CoreStyle::Custom(style_map)) => {
let ov = style_overrides_from_style_map(&node.id, style_map, ctx.caches);
cb = cb.style(move |theme: &iced::Theme, status| {
let mut style = match ov.preset_base.as_deref() {
Some("primary") => checkbox::primary(theme, status),
Some("secondary") => checkbox::secondary(theme, status),
Some("success") => checkbox::success(theme, status),
Some("danger") => checkbox::danger(theme, status),
_ => checkbox::primary(theme, status),
};
apply_checkbox_fields(&mut style, &ov.base);
match status {
checkbox::Status::Hovered { .. } => {
if let Some(ref f) = ov.hovered {
apply_checkbox_fields(&mut style, f);
} else {
style.background = deviate_background(style.background, 0.1);
}
}
checkbox::Status::Disabled { .. } => {
if let Some(ref f) = ov.disabled {
apply_checkbox_fields(&mut style, f);
} else {
style.background = match style.background {
iced::Background::Color(c) => {
iced::Background::Color(alpha_color(c, 0.5))
}
iced::Background::Gradient(g) => {
iced::Background::Gradient(alpha_gradient(g, 0.5))
}
};
if let Some(tc) = style.text_color {
style.text_color = Some(alpha_color(tc, 0.5));
}
style.border = auto_derive_disabled_border(style.border);
}
}
_ => {}
}
style
});
}
None => {}
}
{
let status_wid = ctx.window_id.to_string();
let status_id = node.id.clone();
cb = cb.on_status_change(move |status| Message::Event {
window_id: status_wid.clone(),
id: status_id.clone(),
value: Value::String(status.to_string()),
family: "status".into(),
});
}
container(cb).id(widget::Id::from(node.id.clone())).into()
}