use minui::prelude::*;
use minui::ui::{AutoHide, InteractionCache};
use minui::widgets::controls::scrollbar::{ScrollBar, ScrollBarOptions, ScrollUnit};
use minui::widgets::scroll::{ScrollOrientation, ScrollSize, ScrollState};
use minui::widgets::{WidgetArea, WindowView};
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
const ID_SCROLLBOX: usize = 1;
const ID_VSCROLL: usize = 2;
const ID_HSCROLL: usize = 3;
struct ScrollDemoState {
ui: InteractionCache,
scroll: Rc<RefCell<ScrollState>>,
vbar: ScrollBar,
hbar: ScrollBar,
autohide: AutoHide,
}
fn main() -> minui::Result<()> {
let scroll = Rc::new(RefCell::new(ScrollState::new(
ScrollSize::new(1, 1),
ScrollSize::new(1, 1),
)));
let vbar = ScrollBar::new(
1,
1,
Rc::clone(&scroll),
ScrollBarOptions {
orientation: ScrollOrientation::Vertical,
show_arrows: true,
scroll_step: Some(1),
..ScrollBarOptions::default()
},
);
let hbar = ScrollBar::new(
1,
1,
Rc::clone(&scroll),
ScrollBarOptions {
orientation: ScrollOrientation::Horizontal,
show_arrows: true,
scroll_step: Some(2),
..ScrollBarOptions::default()
},
);
let initial = ScrollDemoState {
ui: InteractionCache::new(),
scroll,
vbar,
hbar,
autohide: AutoHide::new(Duration::from_millis(900), 2),
};
let mut app = App::new(initial)?;
app.run(
|state, event| {
state.ui.observe_event(&event);
if matches!(event, Event::Character('q')) {
return false;
}
match event {
Event::MouseScroll { delta } => {
state.autohide.mark_activity();
let dy: i16 = -(delta as i16);
state
.scroll
.borrow_mut()
.scroll_by(ScrollOrientation::Vertical, dy);
}
Event::MouseScrollHorizontal { delta } => {
state.autohide.mark_activity();
let dx: i16 = -(delta as i16);
state
.scroll
.borrow_mut()
.scroll_by(ScrollOrientation::Horizontal, dx);
}
_ => {}
}
match event {
Event::KeyUp => state
.vbar
.scroll_by_fraction(-1.0 / 5.0, ScrollUnit::Viewport),
Event::KeyDown => state
.vbar
.scroll_by_fraction(1.0 / 5.0, ScrollUnit::Viewport),
Event::KeyLeft => state
.hbar
.scroll_by_fraction(-1.0 / 5.0, ScrollUnit::Viewport),
Event::KeyRight => state
.hbar
.scroll_by_fraction(1.0 / 5.0, ScrollUnit::Viewport),
_ => {}
}
if let Some(entry) = state.ui.get(ID_VSCROLL) {
let _ = state.vbar.handle_event(&event, entry.area);
}
if let Some(entry) = state.ui.get(ID_HSCROLL) {
let _ = state.hbar.handle_event(&event, entry.area);
}
true
},
|state, window| {
state.ui.begin_frame();
let (term_w, term_h) = window.get_size();
let margin: u16 = 1;
let header_h: u16 = 1;
let vbar_w: u16 = 1;
let hbar_h: u16 = 1;
let box_border: u16 = 1; let box_padding: u16 = 1;
let outer_x = margin;
let outer_y = margin + header_h;
let inner_inset_x = box_border + box_padding;
let inner_inset_y = box_border + box_padding;
let max_outer_w = term_w.saturating_sub(margin * 2).saturating_sub(vbar_w);
let max_outer_h = term_h
.saturating_sub(margin * 2)
.saturating_sub(header_h)
.saturating_sub(hbar_h);
let outer_w = max_outer_w;
let outer_h = max_outer_h;
let viewport_x = outer_x + inner_inset_x;
let viewport_y = outer_y + inner_inset_y;
let viewport_w = outer_w.saturating_sub(inner_inset_x * 2);
let viewport_h = outer_h.saturating_sub(inner_inset_y * 2);
let _ = (viewport_x, viewport_y, viewport_w, viewport_h);
window.write_str(
margin,
margin,
"Scroll demo: wheel/drag scrollbars • arrows/keys work • press 'q' to quit",
)?;
let mut content = Container::vertical().with_row_gap(Gap::Pixels(1));
for i in 0..150u16 {
content = content.add_child(
Label::new(format!(
"Line {:03} | The quick brown fox jumps over the lazy dog. {}",
i,
if i % 5 == 0 { "[drag the thumb]" } else { "" }
))
.with_text_color(Color::White),
);
}
let scrollbox = ScrollBox::both()
.with_state(Rc::clone(&state.scroll))
.with_position_and_size(outer_x, outer_y, outer_w, outer_h)
.with_border()
.with_border_chars(BorderChars::single_line())
.with_title("Scrollable content")
.with_title_alignment(TitleAlignment::Left)
.with_padding(minui::widgets::ContainerPadding::uniform(1))
.with_row_gap(Gap::Pixels(1))
.add_child(content);
state.ui.register_scrollable(
ID_SCROLLBOX,
WidgetArea::new(outer_x, outer_y, outer_w, outer_h),
);
scrollbox.draw(window)?;
let vbar_x = outer_x + outer_w;
let vbar_y = outer_y;
let vbar_h = outer_h;
if vbar_x >= term_w {
return Ok(());
}
state.vbar.set_size(1, vbar_h);
state.vbar.set_show_arrows(true);
state.vbar.set_scroll_step(Some(1));
state.vbar.sync_from_state_and_resize_parts();
let v_area = WidgetArea::new(vbar_x, vbar_y, 1, vbar_h);
let mouse_pos = state.ui.last_mouse_pos();
let near_scrollbox_right_edge = if let Some((mx, my)) = mouse_pos {
let edge_x = outer_x + outer_w.saturating_sub(1);
let edge_strip = WidgetArea::new(edge_x, outer_y, 1, outer_h);
state.autohide.is_point_near_area(mx, my, edge_strip)
} else {
false
};
let show_vbar = state
.autohide
.should_show(v_area, mouse_pos, state.vbar.is_dragging())
|| near_scrollbox_right_edge;
if show_vbar {
state.ui.register_draggable(ID_VSCROLL, v_area);
{
let mut view = WindowView {
window,
x_offset: vbar_x,
y_offset: vbar_y,
scroll_x: 0,
scroll_y: 0,
width: 1,
height: vbar_h,
};
state.vbar.draw(&mut view)?;
}
}
let hbar_x = outer_x;
let hbar_y = outer_y + outer_h;
let hbar_w = outer_w;
if hbar_y >= term_h {
return Ok(());
}
state.hbar.set_size(hbar_w, 1);
state.hbar.set_show_arrows(true);
state.hbar.set_scroll_step(Some(2));
state.hbar.sync_from_state_and_resize_parts();
let h_area = WidgetArea::new(hbar_x, hbar_y, hbar_w, 1);
let mouse_pos = state.ui.last_mouse_pos();
let near_scrollbox_bottom_edge = if let Some((mx, my)) = mouse_pos {
let edge_y = outer_y + outer_h.saturating_sub(1);
let edge_strip = WidgetArea::new(outer_x, edge_y, outer_w, 1);
state.autohide.is_point_near_area(mx, my, edge_strip)
} else {
false
};
let show_hbar = state
.autohide
.should_show(h_area, mouse_pos, state.hbar.is_dragging())
|| near_scrollbox_bottom_edge;
if show_hbar {
state.ui.register_draggable(ID_HSCROLL, h_area);
{
let mut view = WindowView {
window,
x_offset: hbar_x,
y_offset: hbar_y,
scroll_x: 0,
scroll_y: 0,
width: hbar_w,
height: 1,
};
state.hbar.draw(&mut view)?;
}
}
window.end_frame()?;
Ok(())
},
)?;
Ok(())
}