diff --git a/core/src/handle.rs b/core/src/handle.rs index fdb224214..e79df9018 100644 --- a/core/src/handle.rs +++ b/core/src/handle.rs @@ -45,6 +45,18 @@ impl<'a, T> Handle<'a, T> { self } + pub fn on_create(self, callback: F) -> Self + where + F: Fn(&mut Context), + { + let prev = self.cx.current; + self.cx.current = self.entity(); + (callback)(self.cx); + self.cx.current = prev; + + self + } + pub fn bind(self, lens: L, closure: F) -> Self where L: Lens, @@ -375,4 +387,6 @@ impl<'a, T> Handle<'a, T> { set_style!(border_radius_top_right, Units); set_style!(border_radius_bottom_left, Units); set_style!(border_radius_bottom_right, Units); + + } diff --git a/core/src/views/mod.rs b/core/src/views/mod.rs index 89836f6f0..7dd3da473 100644 --- a/core/src/views/mod.rs +++ b/core/src/views/mod.rs @@ -57,3 +57,6 @@ pub use self::image::*; mod menu; pub use menu::*; + +mod value_slider; +pub use value_slider::*; diff --git a/core/src/views/textbox.rs b/core/src/views/textbox.rs index b81f05066..1bed012a6 100644 --- a/core/src/views/textbox.rs +++ b/core/src/views/textbox.rs @@ -28,6 +28,7 @@ pub struct TextboxData { dragx: f32, on_edit: Option>, on_submit: Option>, + on_edit_end: Option>, } impl TextboxData { @@ -43,6 +44,7 @@ impl TextboxData { dragx: -1.0, on_edit: None, on_submit: None, + on_edit_end: None, } } @@ -404,6 +406,7 @@ pub enum TextEvent { SetCaretEntity(Entity), SetOnEdit(Option>), SetOnSubmit(Option>), + SetOnEditEnd(Option>), } impl Model for TextboxData { @@ -446,6 +449,9 @@ impl Model for TextboxData { TextEvent::StartEdit => { if !cx.current.is_disabled(cx) { self.edit = true; + cx.focused = cx.current; + cx.capture(); + cx.current.set_checked(cx, true); self.selection_entity.set_visibility(cx, Visibility::Visible); self.caret_entity.set_visibility(cx, Visibility::Visible); } @@ -455,6 +461,9 @@ impl Model for TextboxData { self.edit = false; self.selection_entity.set_visibility(cx, Visibility::Invisible); self.caret_entity.set_visibility(cx, Visibility::Invisible); + if let Some(callback) = &self.on_edit_end { + (callback)(cx); + } } TextEvent::Submit => { @@ -523,6 +532,10 @@ impl Model for TextboxData { TextEvent::SetOnSubmit(on_submit) => { self.on_submit = on_submit.clone(); } + + TextEvent::SetOnEditEnd(on_edit_end) => { + self.on_edit_end = on_edit_end.clone(); + } } } } @@ -553,6 +566,7 @@ where dragx: -1.0, on_edit: text_data.on_edit.clone(), on_submit: text_data.on_submit.clone(), + on_edit_end: text_data.on_edit_end.clone(), }; let real_current = cx.current; cx.current = cx.current.parent(&cx.tree).unwrap(); @@ -620,6 +634,15 @@ impl<'a, L: Lens> Handle<'a, Textbox> { self } + + pub fn on_edit_end(self, callback: F) -> Self + where + F: 'static + Fn(&mut Context) + Send + Sync, + { + self.cx.emit_to(self.entity, TextEvent::SetOnEditEnd(Some(Arc::new(callback)))); + + self + } } impl View for Textbox @@ -642,9 +665,9 @@ where // self.edit = true; cx.emit(TextEvent::StartEdit); - cx.focused = cx.current; - cx.capture(); - cx.current.set_checked(cx, true); + // cx.focused = cx.current; + // cx.capture(); + // cx.current.set_checked(cx, true); //} // Hit test diff --git a/core/src/views/value_slider.rs b/core/src/views/value_slider.rs new file mode 100644 index 000000000..e9ad6bed7 --- /dev/null +++ b/core/src/views/value_slider.rs @@ -0,0 +1,184 @@ +use std::marker::PhantomData; + +use crate::{ + Binding, Context, Element, Handle, Lens, LensExt, Model, MouseButton, + Units::*, View, WindowEvent, ZStack, Color, Label, Modifiers, Event, Textbox, Actions +}; + +use super::textbox::TextEvent; + +#[derive(Lens)] +pub struct VSDataInternal { + edit: bool, +} + +pub enum ValueSliderEvent { + SetEdit(bool), + OnSubmit(f32), +} + +impl Model for VSDataInternal { + fn event(&mut self, _cx: &mut Context, event: &mut Event) { + if let Some(value_slider_event) = event.message.downcast() { + match value_slider_event { + ValueSliderEvent::SetEdit(flag) => { + self.edit = *flag; + } + + _=> {} + } + } + } +} + +pub struct ValueSlider { + p: PhantomData, + is_dragging: bool, + on_changing: Option>, +} + +impl ValueSlider +where + L: Lens, +{ + pub fn new(cx: &mut Context, lens: L) -> Handle { + Self { + p: PhantomData::default(), + is_dragging: false, + on_changing: None, + }.build2(cx, |cx|{ + + VSDataInternal { + edit: false, + }.build(cx); + + ZStack::new(cx, |cx|{ + Element::new(cx) + .height(Stretch(1.0)) + .width(Stretch(1.0)) + .background_color(Color::rgb(200, 200, 200)) + .bind(lens.clone(), move |handle, l|{ + let val = *l.get(handle.cx); + handle.width(Percentage(val * 100.0)); + }); + Binding::new(cx, VSDataInternal::edit, move |cx, edit|{ + if *edit.get(cx) { + Textbox::new(cx, lens.clone().map(|val| format!("{:.2}", val))) + .on_create(|cx|{ + cx.emit(TextEvent::StartEdit); + cx.emit(TextEvent::SelectAll); + }) + .on_submit(|cx, txt|{ + if let Ok(val) = txt.parse::() { + let val = val.clamp(0.0, 1.0); + cx.emit(ValueSliderEvent::OnSubmit(val)); + } + }) + .on_edit_end(|cx|{ + cx.emit(ValueSliderEvent::SetEdit(false)); + }) + .child_space(Stretch(1.0)) + .height(Stretch(1.0)) + .width(Stretch(1.0)); + } else { + Label::new(cx, lens.clone().map(|val| format!("{:.2}", val))) + .child_space(Stretch(1.0)) + .height(Stretch(1.0)) + .width(Stretch(1.0)) + .hoverable(false); + }; + }); + }); + }) + } +} + +impl View for ValueSlider +where + L: Lens, +{ + fn event(&mut self, cx: &mut Context, event: &mut Event) { + + if let Some(value_slider_event) = event.message.downcast() { + match value_slider_event { + ValueSliderEvent::OnSubmit(val) => { + + if let Some(callback) = &self.on_changing { + (callback)(cx, *val); + } + } + + _=> {} + } + } + + if let Some(window_event) = event.message.downcast() { + match window_event { + WindowEvent::MouseDown(button) if *button == MouseButton::Left => { + + if cx.modifiers.contains(Modifiers::ALT) { + cx.emit(ValueSliderEvent::SetEdit(true)); + } else { + if let Some(vs_data) = cx.data::() { + if !vs_data.edit { + self.is_dragging = true; + cx.capture(); + + let mut dx = (cx.mouse.left.pos_down.0 + - cx.cache.get_posx(cx.current)) + / cx.cache.get_width(cx.current); + + dx = dx.clamp(0.0, 1.0); + + if let Some(callback) = self.on_changing.take() { + (callback)(cx, dx); + + self.on_changing = Some(callback); + } + } + } + } + } + + WindowEvent::MouseUp(button) if *button == MouseButton::Left => { + self.is_dragging = false; + cx.release(); + } + + WindowEvent::MouseMove(x, _) => { + if self.is_dragging { + + let mut dx = (*x - cx.cache.get_posx(cx.current)) + / cx.cache.get_width(cx.current); + + dx = dx.clamp(0.0, 1.0); + + if let Some(callback) = &self.on_changing { + (callback)(cx, dx); + } + } + } + + _=> {} + } + } + } +} + +impl<'a, L> Handle<'a, ValueSlider> +where + L: Lens, +{ + pub fn on_changing(self, callback: F) -> Self + where + F: 'static + Fn(&mut Context, f32), + { + if let Some(slider) = + self.cx.views.get_mut(&self.entity).and_then(|f| f.downcast_mut::>()) + { + slider.on_changing = Some(Box::new(callback)); + } + + self + } +} \ No newline at end of file diff --git a/examples/value_slider.rs b/examples/value_slider.rs new file mode 100644 index 000000000..87bbbb3ff --- /dev/null +++ b/examples/value_slider.rs @@ -0,0 +1,37 @@ +use vizia::*; + +#[derive(Lens)] +pub struct AppData { + val: f32, +} + +pub enum AppEvent { + SetValue(f32), +} + +impl Model for AppData { + fn event(&mut self, _cx: &mut Context, event: &mut Event) { + if let Some(app_event) = event.message.downcast() { + match app_event { + AppEvent::SetValue(val) => { + self.val = *val; + } + } + } + } +} + +fn main() { + let mut window_description = WindowDescription::new(); + Application::new(window_description, |cx| { + AppData { val: 0.5 }.build(cx); + + + ValueSlider::new(cx, AppData::val) + .on_changing(|cx, val| cx.emit(AppEvent::SetValue(val))) + .width(Pixels(300.0)) + .height(Pixels(50.0)) + .space(Stretch(1.0)); + }) + .run(); +}