use std::any::Any;
use skia_safe::Color;
use crate::animators::{self, FrameTick};
use crate::control::{Control, GestureCx, Handled, Has, part_mut};
use crate::controls::control_style::PrebuiltControlStyle;
use crate::controls::layout::{LayoutProps, SkiaLayout};
use crate::gestures::{Gesture, GestureKind};
use crate::props;
use crate::tree::{Build, ControlId, Cx, Detached, Handle, Mut, Raw, wrong_state};
use crate::types::Dirty;
props!(ToggleProps, ToggleBuild, ToggleSet {
is_toggled / set_is_toggled: bool = false, APPLY;
control_style / set_control_style: PrebuiltControlStyle = PrebuiltControlStyle::Unset, APPLY;
is_animated / set_is_animated: bool = true, NONE;
responds_to_gestures / set_responds_to_gestures: bool = true, NONE;
color_thumb_on / set_color_thumb_on: Option<Color> = None, APPLY;
color_frame_on / set_color_frame_on: Option<Color> = None, APPLY;
color_thumb_off / set_color_thumb_off: Option<Color> = None, APPLY;
color_frame_off / set_color_frame_off: Option<Color> = None, APPLY;
});
pub(crate) type Toggled = Box<dyn FnMut(Raw<'_>, &mut dyn Any, &mut Cx<'_>, bool)>;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum ToggleColor {
ThumbOn,
FrameOn,
ThumbOff,
FrameOff,
}
pub struct SkiaToggle {
layout: SkiaLayout,
pub p: ToggleProps,
pub(crate) id: ControlId,
on_toggled: Option<Toggled>,
reported: Option<bool>,
pending: Option<bool>,
built: Option<PrebuiltControlStyle>,
pinned: [bool; 3],
}
impl SkiaToggle {
pub(crate) fn new() -> Self {
Self {
layout: SkiaLayout::default(),
p: ToggleProps::default(),
id: Handle::<SkiaToggle>::default().id(),
on_toggled: None,
reported: None,
pending: None,
built: None,
pinned: [false; 3],
}
}
pub fn using_control_style(&self) -> PrebuiltControlStyle {
self.built.unwrap_or_else(|| self.p.control_style.resolve())
}
pub(crate) fn color(&self, which: ToggleColor, style_default: Option<Color>) -> Color {
let (set, base) = match which {
ToggleColor::ThumbOn => (self.p.color_thumb_on, Color::WHITE),
ToggleColor::FrameOn => (self.p.color_frame_on, Color::RED),
ToggleColor::ThumbOff => (self.p.color_thumb_off, Color::WHITE),
ToggleColor::FrameOff => (self.p.color_frame_off, Color::from_rgb(0xA9, 0xA9, 0xA9)),
};
set.or(style_default).unwrap_or(base)
}
pub(crate) fn reported(&self) -> Option<bool> {
self.reported
}
pub(crate) fn needs_content(&self) -> bool {
self.built != Some(self.p.control_style.resolve())
}
pub(crate) fn rebuild(&mut self, cx: &mut Cx, build: impl FnOnce(PrebuiltControlStyle) -> Vec<Detached>) {
let style = self.p.control_style.resolve();
let old: Vec<ControlId> = cx.tree.children(self.id).to_vec();
for child in old {
cx.tree.remove_now(child);
}
if let Some(node) = cx.tree.node_mut(self.id) {
let p = &mut node.base.p;
if self.pinned[0] {
p.width_request = -1.0;
}
if self.pinned[1] {
p.height_request = -1.0;
}
if self.pinned[2] {
p.minimum_height_request = -1.0;
}
}
self.pinned = [false; 3];
for child in build(style) {
let id = cx.add_child(self.id, child);
if let Some(node) = cx.tree.node_mut(id) {
node.part = true;
}
}
self.built = Some(style);
}
pub(crate) fn pin_size(&mut self, cx: &mut Cx, width: f32, height: f32) {
let Some(node) = cx.tree.node_mut(self.id) else { return };
let p = &mut node.base.p;
if p.width_request < 0.0 {
p.width_request = width;
self.pinned[0] = true;
}
if p.height_request < 0.0 {
p.height_request = height;
self.pinned[1] = true;
}
cx.tree.invalidate(self.id, Dirty::MEASURE);
}
pub(crate) fn pin_minimum_height(&mut self, cx: &mut Cx, height: f32) {
let Some(node) = cx.tree.node_mut(self.id) else { return };
if node.base.p.minimum_height_request < 0.0 {
node.base.p.minimum_height_request = height;
self.pinned[2] = true;
cx.tree.invalidate(self.id, Dirty::MEASURE);
}
}
pub(crate) fn take_change(&mut self, cx: &mut Cx) -> bool {
let value = self.p.is_toggled;
let changed = self.reported.is_some_and(|reported| reported != value);
self.reported = Some(value);
if changed && self.on_toggled.is_some() && self.pending.replace(value).is_none() {
animators::start_frame(cx.tree, self.id, fire);
}
changed
}
pub(crate) fn tap(&mut self, cx: &mut GestureCx) -> Handled {
if !self.p.responds_to_gestures {
return Handled::No;
}
self.p.is_toggled = !self.p.is_toggled;
cx.invalidate(Dirty::APPLY);
Handled::Tapped
}
}
fn fire(id: ControlId, _time_ms: f64, state: &mut dyn Any, cx: &mut Cx<'_>) -> FrameTick {
let mut tick = FrameTick { keep: false, state_touched: false };
let Some(mut node) = cx.tree.take(id) else { return tick };
let mut queue = Vec::new();
if let Some(control) = node.kind.as_deref_mut() {
let due = part_mut::<SkiaToggle>(control).and_then(|t| Some((t.pending.take()?, t.on_toggled.take()?)));
if let Some((value, mut handler)) = due {
handler(Raw { id, control, base: &mut node.base, queue: &mut queue }, state, &mut Cx { tree: cx.tree }, value);
if let Some(toggle) = part_mut::<SkiaToggle>(control) {
toggle.on_toggled = Some(handler);
}
tick.state_touched = true;
}
}
cx.tree.put_back(node);
cx.tree.queue.append(&mut queue);
tick
}
impl Has<ToggleProps> for SkiaToggle {
fn part(&self) -> &ToggleProps {
&self.p
}
fn part_mut(&mut self) -> &mut ToggleProps {
&mut self.p
}
}
impl Has<LayoutProps> for SkiaToggle {
fn part(&self) -> &LayoutProps {
&self.layout.p
}
fn part_mut(&mut self) -> &mut LayoutProps {
&mut self.layout.p
}
}
impl Control for SkiaToggle {
fn receives_hover(&self) -> bool {
true
}
fn inner(&self) -> Option<&dyn Control> {
Some(&self.layout)
}
fn inner_mut(&mut self) -> Option<&mut dyn Control> {
Some(&mut self.layout)
}
fn on_gesture(&mut self, cx: &mut GestureCx, gesture: &Gesture) -> Handled {
match gesture.kind {
GestureKind::Tapped => self.tap(cx),
_ => Handled::No,
}
}
fn accessibility_can_interact(&self) -> Option<bool> {
Some(self.p.responds_to_gestures)
}
fn accessibility_is_pressed(&self) -> Option<bool> {
Some(self.p.is_toggled)
}
}
impl<T: Has<ToggleProps>> Build<T> {
pub fn on_toggled<S: Any>(mut self, mut f: impl FnMut(&mut Mut<'_, T>, &mut S, &mut Cx<'_>, bool) + 'static) -> Self {
let toggle: &mut SkiaToggle = part_mut(self.control_mut()).expect("the control embeds a SkiaToggle");
toggle.on_toggled = Some(Box::new(move |me, state, cx, value| {
let state = state.downcast_mut::<S>().unwrap_or_else(|| wrong_state::<S>());
f(&mut me.typed(), state, cx, value)
}));
self
}
}