use crate::{
pre_hooks,
props::Props,
unpack_named_slots, widget,
widget::{
component::{
containers::content_box::content_box,
image_box::{image_box, ImageBoxProps},
interactive::{
button::{button, ButtonNotifyMessage, ButtonNotifyProps},
navigation::{
use_nav_container_active, use_nav_item, use_nav_item_active,
use_nav_scroll_view_content, NavButtonTrackingActive, NavItemActive, NavJump,
NavScroll, NavSignal,
},
scroll_view::{use_scroll_view, ScrollViewState},
},
use_resize_listener, ResizeListenerSignal,
},
context::WidgetContext,
node::WidgetNode,
unit::{area::AreaBoxNode, content::ContentBoxItemLayout, image::ImageBoxMaterial},
utils::{Rect, Vec2},
WidgetId,
},
Scalar,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScrollBoxOwner(
#[serde(default)]
#[serde(skip_serializing_if = "WidgetId::is_none")]
pub WidgetId,
);
implement_props_data!(ScrollBoxOwner);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SideScrollbarsProps {
#[serde(default)]
pub size: Scalar,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub back_material: Option<ImageBoxMaterial>,
#[serde(default)]
pub front_material: ImageBoxMaterial,
}
implement_props_data!(SideScrollbarsProps);
impl Default for SideScrollbarsProps {
fn default() -> Self {
Self {
size: 10.0,
back_material: None,
front_material: Default::default(),
}
}
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
pub struct SideScrollbarsState {
pub horizontal: Scalar,
pub vertical: Scalar,
}
implement_props_data!(SideScrollbarsState);
pub fn use_nav_scroll_box_content(context: &mut WidgetContext) {
context.life_cycle.change(|context| {
for msg in context.messenger.messages {
if let Some(ResizeListenerSignal::Change) = msg.as_any().downcast_ref() {
if let Ok(data) = context.props.read::<ScrollBoxOwner>() {
context
.messenger
.write(data.0.to_owned(), ResizeListenerSignal::Change);
}
}
}
});
}
#[pre_hooks(
use_resize_listener,
use_nav_item_active,
use_nav_container_active,
use_nav_scroll_view_content,
use_nav_scroll_box_content
)]
pub fn nav_scroll_box_content(mut context: WidgetContext) -> WidgetNode {
let WidgetContext {
id, named_slots, ..
} = context;
unpack_named_slots!(named_slots => content);
widget! {{{
AreaBoxNode {
id: id.to_owned(),
slot: Box::new(content),
..Default::default()
}
}}}
}
pub fn use_nav_scroll_box(context: &mut WidgetContext) {
context.life_cycle.change(|context| {
for msg in context.messenger.messages {
if let Some(ResizeListenerSignal::Change) = msg.as_any().downcast_ref() {
if let Ok(data) = context.state.read::<ScrollViewState>() {
context
.signals
.write(NavSignal::Jump(NavJump::Scroll(NavScroll::Factor(
data.value, false,
))));
}
}
}
});
}
#[pre_hooks(
use_resize_listener,
use_nav_item,
use_nav_container_active,
use_scroll_view,
use_nav_scroll_box
)]
pub fn nav_scroll_box(mut context: WidgetContext) -> WidgetNode {
let WidgetContext {
id,
key,
props,
state,
named_slots,
..
} = context;
unpack_named_slots!(named_slots => {content, scrollbars});
let scroll_props = state.read_cloned_or_default::<ScrollViewState>();
let content_props = Props::new(ContentBoxItemLayout {
align: scroll_props.value,
..Default::default()
})
.with(ScrollBoxOwner(id.to_owned()));
if let Some(props) = scrollbars.props_mut() {
props.write(ScrollBoxOwner(id.to_owned()));
props.write(scroll_props);
}
widget! {
(#{key} content_box: {props.clone()} [
(#{"content"} nav_scroll_box_content: {content_props} {
content = {content}
})
{scrollbars}
])
}
}
pub fn use_nav_scroll_box_side_scrollbars(context: &mut WidgetContext) {
context.life_cycle.mount(|context| {
drop(context.state.write_with(SideScrollbarsState::default()));
});
context.life_cycle.change(|context| {
for msg in context.messenger.messages {
if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {
if msg.state.selected && (msg.state.trigger || msg.state.context) {
let mut dirty = false;
let mut data = context
.state
.read_cloned_or_default::<SideScrollbarsState>();
if msg.sender.key() == "hbar" {
data.horizontal = msg.state.pointer.x;
dirty = true;
} else if msg.sender.key() == "vbar" {
data.vertical = msg.state.pointer.y;
dirty = true;
}
if dirty {
let view = context.props.read_cloned_or_default::<ScrollBoxOwner>().0;
let pos = Vec2 {
x: data.horizontal,
y: data.vertical,
};
context.signals.write(NavSignal::Jump(NavJump::Scroll(
NavScroll::DirectFactor(view.into(), pos, false),
)));
drop(context.state.write_with(data));
}
}
}
}
});
}
#[pre_hooks(
use_nav_item_active,
use_nav_container_active,
use_nav_scroll_box_side_scrollbars
)]
pub fn nav_scroll_box_side_scrollbars(mut context: WidgetContext) -> WidgetNode {
let WidgetContext { id, key, props, .. } = context;
let view_props = props.read_cloned_or_default::<ScrollViewState>();
let SideScrollbarsProps {
size,
back_material,
front_material,
} = props.read_cloned_or_default();
let hbar = if view_props.size_factor.x > 1.0 {
let props = Props::new(ButtonNotifyProps(id.to_owned().into()))
.with(NavItemActive)
.with(NavButtonTrackingActive)
.with(ContentBoxItemLayout {
anchors: Rect {
left: 0.0,
right: 1.0,
top: 1.0,
bottom: 1.0,
},
margin: Rect {
left: 0.0,
right: size,
top: -size,
bottom: 0.0,
},
align: Vec2 { x: 0.0, y: 1.0 },
..Default::default()
});
let front_props = ImageBoxProps {
material: front_material.clone(),
..Default::default()
};
let back = if let Some(material) = back_material.clone() {
let props = ImageBoxProps {
material,
..Default::default()
};
widget! { (#{"back"} image_box: {props}) }
} else {
widget! {()}
};
widget! {
(#{"hbar"} button: {props} {
content = (#{"container"} content_box [
{back}
(#{"front"} image_box: {front_props})
])
})
}
} else {
widget! {()}
};
let vbar = if view_props.size_factor.y > 1.0 {
let props = Props::new(ButtonNotifyProps(id.to_owned().into()))
.with(NavItemActive)
.with(NavButtonTrackingActive)
.with(ContentBoxItemLayout {
anchors: Rect {
left: 1.0,
right: 1.0,
top: 0.0,
bottom: 1.0,
},
margin: Rect {
left: -size,
right: 0.0,
top: 0.0,
bottom: size,
},
align: Vec2 { x: 1.0, y: 0.0 },
..Default::default()
});
let back = if let Some(material) = back_material {
let props = ImageBoxProps {
material,
..Default::default()
};
widget! { (#{"back"} image_box: {props}) }
} else {
widget! {()}
};
let front_props = ImageBoxProps {
material: front_material,
..Default::default()
};
widget! {
(#{"vbar"} button: {props} {
content = (#{"container"} content_box [
{back}
(#{"front"} image_box: {front_props})
])
})
}
} else {
widget! {()}
};
widget! {
(#{key} content_box [
{hbar}
{vbar}
])
}
}