use crate::{
direction::{Direction, Orientation},
event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent},
theme::PaletteStyle,
view::{CannotFocus, View},
Cursive, Printer, Vec2, With,
};
use std::rc::Rc;
type SliderCallback = dyn Fn(&mut Cursive, usize);
pub struct SliderView {
orientation: Orientation,
on_change: Option<Rc<SliderCallback>>,
on_enter: Option<Rc<SliderCallback>>,
value: usize,
max_value: usize,
dragging: bool,
}
impl SliderView {
pub fn new(orientation: Orientation, max_value: usize) -> Self {
SliderView {
orientation,
value: 0,
max_value,
on_change: None,
on_enter: None,
dragging: false,
}
}
pub fn vertical(max_value: usize) -> Self {
Self::new(Orientation::Vertical, max_value)
}
pub fn horizontal(max_value: usize) -> Self {
Self::new(Orientation::Horizontal, max_value)
}
pub fn set_value(&mut self, value: usize) -> EventResult {
self.value = value;
self.get_change_result()
}
#[must_use]
pub fn value(self, value: usize) -> Self {
self.with(|s| {
s.set_value(value);
})
}
pub fn get_value(&self) -> usize {
self.value
}
pub fn get_max_value(&self) -> usize {
self.max_value
}
#[must_use]
pub fn on_change<F>(mut self, callback: F) -> Self
where
F: Fn(&mut Cursive, usize) + 'static,
{
self.on_change = Some(Rc::new(callback));
self
}
#[must_use]
pub fn on_enter<F>(mut self, callback: F) -> Self
where
F: Fn(&mut Cursive, usize) + 'static,
{
self.on_enter = Some(Rc::new(callback));
self
}
fn get_change_result(&self) -> EventResult {
EventResult::Consumed(self.on_change.clone().map(|cb| {
let value = self.value;
Callback::from_fn(move |s| {
cb(s, value);
})
}))
}
fn slide_plus(&mut self) -> EventResult {
if self.value + 1 < self.max_value {
self.value += 1;
self.get_change_result()
} else {
EventResult::Ignored
}
}
fn slide_minus(&mut self) -> EventResult {
if self.value > 0 {
self.value -= 1;
self.get_change_result()
} else {
EventResult::Ignored
}
}
fn req_size(&self) -> Vec2 {
self.orientation.make_vec(self.max_value, 1)
}
}
impl View for SliderView {
fn draw(&self, printer: &Printer) {
match self.orientation {
Orientation::Vertical => {
printer.print_vline((0, 0), self.max_value, "|")
}
Orientation::Horizontal => {
printer.print_hline((0, 0), self.max_value, "-")
}
}
let style = if printer.focused {
PaletteStyle::Highlight
} else {
PaletteStyle::HighlightInactive
};
printer.with_style(style, |printer| {
printer.print(self.orientation.make_vec(self.value, 0), " ");
});
}
fn required_size(&mut self, _: Vec2) -> Vec2 {
self.req_size()
}
fn on_event(&mut self, event: Event) -> EventResult {
match event {
Event::Key(Key::Left)
if self.orientation == Orientation::Horizontal =>
{
self.slide_minus()
}
Event::Key(Key::Right)
if self.orientation == Orientation::Horizontal =>
{
self.slide_plus()
}
Event::Key(Key::Up)
if self.orientation == Orientation::Vertical =>
{
self.slide_minus()
}
Event::Key(Key::Down)
if self.orientation == Orientation::Vertical =>
{
self.slide_plus()
}
Event::Key(Key::Enter) if self.on_enter.is_some() => {
let value = self.value;
let cb = self.on_enter.clone().unwrap();
EventResult::with_cb(move |s| {
cb(s, value);
})
}
Event::Mouse {
event: MouseEvent::Hold(MouseButton::Left),
position,
offset,
} if self.dragging => {
let position = position.saturating_sub(offset);
let position = self.orientation.get(&position);
let position = ::std::cmp::min(
position,
self.max_value.saturating_sub(1),
);
self.value = position;
self.get_change_result()
}
Event::Mouse {
event: MouseEvent::Press(MouseButton::Left),
position,
offset,
} if position.fits_in_rect(offset, self.req_size()) => {
if let Some(position) = position.checked_sub(offset) {
self.dragging = true;
self.value = self.orientation.get(&position);
}
self.get_change_result()
}
Event::Mouse {
event: MouseEvent::Release(MouseButton::Left),
..
} => {
self.dragging = false;
EventResult::Ignored
}
_ => EventResult::Ignored,
}
}
fn take_focus(
&mut self,
_: Direction,
) -> Result<EventResult, CannotFocus> {
Ok(EventResult::Consumed(None))
}
}