use std::hash::Hash;
use crate::base_types::*;
use crate::component::{Component, ComponentHasher, Message, RenderContext};
use crate::event;
use crate::font_cache::HorizontalAlign;
use crate::layout::*;
use crate::render::{renderables::shape::Shape, Renderable};
use crate::style::{current_style, Styled};
use crate::{node, txt, Node};
use lemna_macros::{component, state_component_impl};
#[derive(Debug)]
enum SelectMessage {
OpenClose,
Close,
Hover(usize),
Select(usize),
}
#[derive(Debug, Default)]
struct SelectState {
open: bool,
selected: usize,
hovering: usize,
}
#[component(State = "SelectState", Styled, Internal)]
pub struct Select<M: Send + Sync>
where
M: Send + Sync,
{
pub selection: Vec<M>,
pub selected: usize,
on_change: Option<Box<dyn Fn(usize, &M) -> Message + Send + Sync>>,
}
impl<M: std::fmt::Debug + Send + Sync> std::fmt::Debug for Select<M> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Select")
.field("selection", &self.selection)
.finish()
}
}
impl<M: ToString + Send + Sync> Select<M> {
pub fn new(selection: Vec<M>, selected: usize) -> Self {
Self {
selection,
selected,
on_change: None,
class: Default::default(),
style_overrides: Default::default(),
state: Some(SelectState::default()),
dirty: false,
}
}
pub fn on_change(mut self, change_fn: Box<dyn Fn(usize, &M) -> Message + Send + Sync>) -> Self {
self.on_change = Some(change_fn);
self
}
}
#[state_component_impl(SelectState)]
impl<M: 'static + std::fmt::Debug + Clone + ToString + std::fmt::Display + Send + Sync> Component
for Select<M>
{
fn view(&self) -> Option<Node> {
let mut base =
node!(super::Div::new(), lay!(direction: Direction::Column)).push(node!(SelectBox {
selection: self.selection.get(self.state_ref().selected).cloned(),
style_overrides: self.style_overrides.clone(),
class: self.class.clone(),
}));
if self.state_ref().open {
base = base.push(node!(
SelectList {
selections: self.selection.clone(),
hovering: self.state_ref().hovering,
style_overrides: self.style_overrides.clone(),
class: self.class.clone(),
},
lay!(position_type: PositionType::Absolute, z_index_increment: 1000.0),
1
));
}
Some(base)
}
fn props_hash(&self, hasher: &mut ComponentHasher) {
self.selected.hash(hasher);
}
fn init(&mut self) {
self.state_mut().selected = self.selected;
}
fn new_props(&mut self) {
self.state_mut().selected = self.selected;
}
fn render_hash(&self, hasher: &mut ComponentHasher) {
self.state_ref().open.hash(hasher)
}
fn update(&mut self, message: Message) -> Vec<Message> {
let mut m: Vec<Message> = vec![];
match message.downcast_ref::<SelectMessage>() {
Some(SelectMessage::OpenClose) => {
self.state_mut().hovering = self.state_ref().selected;
self.state_mut().open = !self.state_ref().open;
}
Some(SelectMessage::Close) => self.state_mut().open = false,
Some(SelectMessage::Select(i)) => {
self.state_mut().selected = *i;
if let Some(change_fn) = &self.on_change {
m.push(change_fn(*i, &self.selection[*i]))
}
}
Some(SelectMessage::Hover(i)) => self.state_mut().hovering = *i,
_ => panic!(),
}
m
}
}
#[component(Styled = "Select", Internal)]
#[derive(Debug)]
struct SelectBox<M> {
selection: Option<M>,
}
impl<M: 'static + std::fmt::Debug + Clone + ToString> Component for SelectBox<M> {
fn view(&self) -> Option<Node> {
let padding: f64 = self.style_val("padding").unwrap().into();
let radius: f32 = self.style_val("radius").unwrap().f32();
let font_size: f32 = self.style_val("font_size").unwrap().f32();
let background_color: Color = self.style_val("background_color").into();
let border_color: Color = self.style_val("border_color").into();
let caret_color: Color = self.style_val("caret_color").into();
let border_width: f32 = self.style_val("border_width").unwrap().f32();
let mut base = node!(
super::RoundedRect {
background_color: background_color,
border_color: border_color,
border_width: border_width,
radius: (radius, radius, radius, radius),
},
lay!(
size: size_pct!(100.0),
padding: rect!(padding),
cross_alignment: Alignment::Center,
axis_alignment: Alignment::Center,
direction: Direction::Row,
)
);
if let Some(selection) = self.selection.as_ref() {
base = base
.push(node!(super::Text::new(txt!(selection.to_string()))
.style("size", self.style_val("font_size").unwrap())
.style("color", self.style_val("text_color").unwrap())
.style("h_alignment", HorizontalAlign::Center)
.maybe_style("font", self.style_val("font"))))
.push(node!(
Caret { color: caret_color },
lay!(
size: size!(font_size / 2.0),
margin: rect!(Auto, padding)
)
))
}
Some(base)
}
fn on_mouse_motion(&mut self, event: &mut event::Event<event::MouseMotion>) {
event.stop_bubbling();
}
fn on_click(&mut self, event: &mut event::Event<event::Click>) {
event.focus();
event.stop_bubbling();
event.emit(Box::new(SelectMessage::OpenClose));
}
fn on_blur(&mut self, event: &mut event::Event<event::Blur>) {
event.emit(Box::new(SelectMessage::Close));
}
}
#[derive(Debug)]
struct Caret {
color: Color,
}
use lyon::path::Path;
use lyon::tessellation::math as lyon_math;
impl Component for Caret {
fn render(&mut self, context: RenderContext) -> Option<Vec<Renderable>> {
let scale = 1.0;
let mut path_builder = Path::builder();
let w = context.aabb.width();
let h = context.aabb.height();
let start = h / 2.0;
path_builder.move_to(lyon_math::point(0.0, start));
path_builder.line_to(lyon_math::point(w / 2.0, h));
path_builder.line_to(lyon_math::point(w, start));
let (geometry, _) = Shape::path_to_shape_geometry(path_builder.build(), false, true);
Some(vec![Renderable::Shape(Shape::stroke(
geometry,
self.color,
scale,
0.0,
&mut context.caches.shape_buffer_cache.write().unwrap(),
context.prev_state.as_ref().and_then(|v| match v.get(0) {
Some(Renderable::Shape(r)) => Some(r.buffer_id),
_ => None,
}),
))])
}
}
#[derive(Debug)]
#[component(Styled = "Select", Internal)]
struct SelectList<M>
where
M: Send + Sync,
{
selections: Vec<M>,
hovering: usize,
}
impl<M: 'static + std::fmt::Debug + Clone + ToString + Send + Sync> Component for SelectList<M> {
fn view(&self) -> Option<Node> {
let background_color: Color = self.style_val("background_color").into();
let mut l = node!(
super::Div::new().bg(background_color).scroll_y(),
[direction: Column, cross_alignment: Stretch,]
);
for (i, s) in self.selections.iter().enumerate() {
l = l.push(
node!(SelectEntry {
selection: s.clone(),
id: i,
selected: i == self.hovering,
style_overrides: self.style_overrides.clone(),
class: self.class.clone(),
})
.key(i as u64),
);
}
Some(l)
}
fn full_control(&self) -> bool {
true
}
fn set_aabb(
&mut self,
aabb: &mut AABB,
parent_aabb: AABB,
mut children: Vec<(&mut AABB, Option<Scale>, Option<Point>)>,
frame: AABB,
scale_factor: f32,
) {
if let Some((child_aabb, Some(inner_scale), _)) = children.first_mut() {
let max_height: f32 = self.style_val("max_height").unwrap().f32();
let bar_width: f32 = current_style("Scroll", "bar_width").unwrap().f32();
let mut h = inner_scale.height;
let mut w = inner_scale.width;
if h > max_height * scale_factor {
h = max_height * scale_factor;
w = inner_scale.width + bar_width * scale_factor;
}
let room_above = parent_aabb.pos.y - frame.pos.y;
let room_bellow = frame.bottom_right.y - parent_aabb.bottom_right.y;
if h > room_bellow && h > room_above {
h = room_bellow.max(room_above);
w = inner_scale.width + bar_width * scale_factor;
}
aabb.set_scale_mut(w, h);
child_aabb.set_scale_mut(w, h);
}
if aabb.bottom_right.y > frame.bottom_right.y {
aabb.translate_mut(0.0, -parent_aabb.height() - aabb.height());
}
}
}
#[component(Styled = "Select", Internal)]
#[derive(Debug)]
struct SelectEntry<M>
where
M: Send + Sync,
{
selection: M,
id: usize,
selected: bool,
}
impl<M: 'static + std::fmt::Debug + Clone + ToString + Send + Sync> Component for SelectEntry<M> {
fn view(&self) -> Option<Node> {
let padding: f64 = self.style_val("padding").unwrap().into();
let highlight_color: Color = self.style_val("highlight_color").into();
let mut div = super::Div::new();
if self.selected {
div = div.bg(highlight_color)
}
Some(
node!(div, lay!(size: size_pct!(100.0), padding: rect!(padding))).push(node!(
super::Text::new(txt!(self.selection.to_string()))
.style("size", self.style_val("font_size").unwrap())
.style("color", self.style_val("text_color").unwrap())
.style("h_alignment", HorizontalAlign::Center)
.maybe_style("font", self.style_val("font"))
)),
)
}
fn on_mouse_motion(&mut self, event: &mut event::Event<event::MouseMotion>) {
event.stop_bubbling();
}
fn on_mouse_enter(&mut self, event: &mut event::Event<event::MouseEnter>) {
event.emit(Box::new(SelectMessage::Hover(self.id)));
}
fn on_click(&mut self, event: &mut event::Event<event::Click>) {
event.stop_bubbling();
event.emit(Box::new(SelectMessage::Select(self.id)));
event.emit(Box::new(SelectMessage::Close));
}
}