use std::cell::RefCell;
use std::rc::{Rc, Weak};
use crate::{
Callback, ControlContext, ControlEvent, ControlObject, EventContext, FuiDrawingContext, Point,
Rect, Size, Style, StyledControl, ViewContext,
};
use typed_builder::TypedBuilder;
pub enum RelativePlacement {
FullSize,
BelowOrAboveControl(Weak<RefCell<dyn ControlObject>>),
LeftOrRightControl(Weak<RefCell<dyn ControlObject>>),
}
#[derive(Copy, Clone)]
pub enum RelativeAutoHide {
None,
ClickedOutside,
Menu,
}
#[derive(TypedBuilder)]
pub struct RelativeLayout {
#[builder(default = RelativePlacement::FullSize)]
pub placement: RelativePlacement,
#[builder(default = RelativeAutoHide::None)]
pub auto_hide: RelativeAutoHide,
#[builder(default = Callback::empty())]
pub auto_hide_request: Callback<()>,
#[builder(default = Vec::new())]
pub uncovered_controls: Vec<Weak<RefCell<dyn ControlObject>>>,
}
impl RelativeLayout {
pub fn to_view(
self,
style: Option<Box<dyn Style<Self>>>,
context: ViewContext,
) -> Rc<RefCell<dyn ControlObject>> {
StyledControl::new(
self,
style.unwrap_or_else(|| {
Box::new(DefaultRelativeLayoutStyle::new(
DefaultRelativeLayoutStyleParams::builder().build(),
))
}),
context,
)
}
}
#[derive(TypedBuilder)]
pub struct DefaultRelativeLayoutStyleParams {}
pub struct DefaultRelativeLayoutStyle {
rect: Rect,
relative_control_rect: Rect,
}
impl DefaultRelativeLayoutStyle {
pub fn new(_params: DefaultRelativeLayoutStyleParams) -> Self {
DefaultRelativeLayoutStyle {
rect: Rect::empty(),
relative_control_rect: Rect::empty(),
}
}
}
impl Style<RelativeLayout> for DefaultRelativeLayoutStyle {
fn setup(&mut self, _data: &mut RelativeLayout, _control_context: &mut ControlContext) {}
fn handle_event(
&mut self,
data: &mut RelativeLayout,
_control_context: &mut ControlContext,
_drawing_context: &mut FuiDrawingContext,
_event_context: &mut dyn EventContext,
event: ControlEvent,
) {
match event {
ControlEvent::TapDown { .. } => match data.auto_hide {
RelativeAutoHide::ClickedOutside | RelativeAutoHide::Menu => {
data.auto_hide_request.emit(())
}
_ => (),
},
ControlEvent::PointerMove { position } => match data.auto_hide {
RelativeAutoHide::Menu => {
if !position.is_inside(&self.relative_control_rect)
&& !position.is_inside(&self.rect)
{
data.auto_hide_request.emit(())
}
}
_ => (),
},
_ => (),
}
}
fn measure(
&mut self,
_data: &mut RelativeLayout,
_control_context: &mut ControlContext,
_drawing_context: &mut FuiDrawingContext,
size: Size,
) -> Size {
self.rect = Rect::new(0.0f32, 0.0f32, size.width, size.height);
size
}
fn set_rect(
&mut self,
data: &mut RelativeLayout,
control_context: &mut ControlContext,
drawing_context: &mut FuiDrawingContext,
rect: Rect,
) {
let children = control_context.get_children();
let mut is_above = false;
let mut is_left = false;
self.relative_control_rect = Rect::new(0.0f32, 0.0f32, 0.0f32, 0.0f32);
let available_size = match &data.placement {
RelativePlacement::FullSize => Size::new(rect.width, rect.height),
RelativePlacement::BelowOrAboveControl(relative_control) => {
match relative_control.upgrade() {
Some(relative_control) => {
self.relative_control_rect = relative_control.borrow().get_rect();
let height_above = self.relative_control_rect.y;
let height_below = rect.height
- (self.relative_control_rect.y + self.relative_control_rect.height);
if height_above > height_below {
is_above = true;
Size::new(self.relative_control_rect.width, height_above)
} else {
Size::new(self.relative_control_rect.width, height_below)
}
}
_ => Size::new(rect.width, rect.height),
}
}
RelativePlacement::LeftOrRightControl(relative_control) => {
match relative_control.upgrade() {
Some(relative_control) => {
self.relative_control_rect = relative_control.borrow().get_rect();
let width_left = self.relative_control_rect.x;
let width_right = rect.width
- (self.relative_control_rect.x + self.relative_control_rect.width);
if width_left > width_right {
is_left = true;
Size::new(width_left, rect.height)
} else {
Size::new(width_right, rect.height)
}
}
_ => Size::new(rect.width, rect.height),
}
}
};
let content_size = match children.into_iter().next() {
Some(ref content) => {
content
.borrow_mut()
.measure(drawing_context, available_size);
let rect = content.borrow().get_rect();
Size::new(rect.width, rect.height)
}
_ => Size::new(0f32, 0f32),
};
self.rect = match &data.placement {
RelativePlacement::FullSize => {
Rect::new(0.0f32, 0.0f32, available_size.width, available_size.height)
}
RelativePlacement::BelowOrAboveControl(_) => {
if is_above {
Rect::new(
self.relative_control_rect.x,
self.relative_control_rect.y - content_size.height,
content_size.width.max(available_size.width),
content_size.height,
)
} else {
Rect::new(
self.relative_control_rect.x,
self.relative_control_rect.y + self.relative_control_rect.height,
content_size.width.max(available_size.width),
content_size.height,
)
}
}
RelativePlacement::LeftOrRightControl(_) => {
let pos_y = if content_size.height
<= available_size.height - self.relative_control_rect.y
{
self.relative_control_rect.y
} else {
(self.relative_control_rect.y + self.relative_control_rect.height
- content_size.height)
.max(0.0f32)
};
if is_left {
Rect::new(
self.relative_control_rect.x - content_size.width.min(available_size.width),
pos_y,
content_size.width.min(available_size.width),
content_size.height,
)
} else {
Rect::new(
self.relative_control_rect.x + self.relative_control_rect.width,
pos_y,
content_size.width.min(available_size.width),
content_size.height,
)
}
}
};
let children = control_context.get_children();
if let Some(ref content) = children.into_iter().next() {
content.borrow_mut().set_rect(drawing_context, self.rect);
}
}
fn hit_test(
&self,
data: &RelativeLayout,
control_context: &ControlContext,
point: Point,
) -> Option<Rc<RefCell<dyn ControlObject>>> {
if point.is_inside(&self.rect) {
let children = control_context.get_children();
if let Some(ref content) = children.into_iter().next() {
let c = content.borrow();
let rect = c.get_rect();
if point.is_inside(&rect) {
let hit_control = c.hit_test(point);
if hit_control.is_some() {
return hit_control;
}
}
}
}
for uncovered_control in &data.uncovered_controls {
if let Some(uncovered_control) = uncovered_control.upgrade() {
if point.is_inside(&uncovered_control.borrow().get_rect()) {
return None;
}
}
}
Some(control_context.get_self_rc())
}
fn draw(
&mut self,
_data: &RelativeLayout,
control_context: &ControlContext,
drawing_context: &mut FuiDrawingContext,
) {
let children = control_context.get_children();
match children.into_iter().next() {
Some(child) => child.borrow_mut().draw(drawing_context),
_ => (),
}
}
}