use std::{
cell::RefCell,
rc::{Rc, Weak},
};
use super::*;
use crate::canvas::GlyphArray;
pub trait Scrollable: Window {
fn is_scrollable(&self, axis: Orientation) -> bool;
fn scroll(&mut self, direction: Direction, kind: ScrollKind);
fn set_offset(&mut self, direction: Orientation, offset: usize);
fn offset(&self, axis: Orientation) -> usize;
fn viewport(&self, axis: Orientation) -> usize;
fn set_scroll_viewer(&mut self, scroll_viewer: Weak<RefCell<ScrollViewer>>);
}
#[derive(Clone, Debug, Default)]
pub struct ScrollBase {
pub offset: Vector2D<usize>,
pub viewport: Vector2D<usize>,
pub render_size: TPoint,
pub scroll_viewer: Weak<RefCell<ScrollViewer>>,
}
impl ScrollBase {
pub fn is_scrollable(&self, axis: Orientation) -> bool {
let idx = axis as usize;
self.scroll_viewer.strong_count() > usize::MIN
&& ((self.offset[idx] == usize::MIN
&& self.viewport[idx] > self.render_size[idx] as usize)
|| self.offset[idx] > usize::MIN)
}
pub fn scroll(&mut self, direction: Direction, kind: ScrollKind) {
let delta = match kind {
ScrollKind::Line => 1,
ScrollKind::Page => match direction {
Direction::Up | Direction::Down => self.render_size.y as usize,
Direction::Left | Direction::Right => self.render_size.x as usize,
},
};
match direction {
Direction::Up => {
if let Some(offset) = self.offset.y.checked_sub(delta) {
self.offset.y = offset;
}
else {
self.offset.y = usize::MIN;
}
}
Direction::Down => {
let offset = self.offset.y + delta;
if offset <= self.viewport.y - delta {
self.offset.y += delta;
}
else {
self.offset.y = self.viewport.y - delta;
}
}
Direction::Left => {
if let Some(offset) = self.offset.x.checked_sub(delta) {
self.offset.x -= offset;
}
else {
self.offset.x = usize::MIN
}
}
Direction::Right => {
let offset = self.offset.x + delta;
if offset <= self.viewport.x - delta {
self.offset.x += delta;
}
else {
self.offset.x = self.viewport.x - delta;
}
}
}
}
pub fn set_offset(&mut self, direction: Orientation, offset: usize) {
match direction {
Orientation::Horizontal => {
if offset < self.viewport.x {
self.offset.x = offset;
}
else {
self.offset.x = self.viewport.x.checked_sub(1).unwrap_or_default();
}
}
Orientation::Vertical => {
if offset < self.viewport.y {
self.offset.y = offset;
}
else {
self.offset.y = self.viewport.y.checked_sub(1).unwrap_or_default();
}
}
}
}
pub fn offset(&self, axis: Orientation) -> usize {
match axis {
Orientation::Horizontal => self.offset.x,
Orientation::Vertical => self.offset.y,
}
}
pub fn viewport(&self, axis: Orientation) -> usize {
match axis {
Orientation::Horizontal => self.viewport.x,
Orientation::Vertical => self.viewport.y,
}
}
pub fn set_scroll_viewer(&mut self, scroll_viewer: Weak<RefCell<ScrollViewer>>) {
self.scroll_viewer = scroll_viewer;
}
}
#[derive(Clone, Copy, Debug)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Copy, Clone, Debug)]
pub enum ScrollKind {
Line,
Page,
}
#[derive(Default)]
pub struct ScrollViewer {
base: WidgetBase,
draw_bars: Vector2D<bool>,
content: Option<Rc<RefCell<dyn Scrollable>>>,
show_scrollbar: bool,
size: Vector2D<TSize>,
}
impl ScrollViewer {
pub const ARROW_UP: Grapheme = Grapheme::new_unchecked("▲", GlyphWidth::Half);
pub const ARROW_UP_DISABLED: Grapheme = Grapheme::new_unchecked("△", GlyphWidth::Half);
pub const BAR_MARKER_V: Grapheme = Grapheme::new_unchecked("┃", GlyphWidth::Half);
pub const ARROW_DOWN: Grapheme = Grapheme::new_unchecked("▼", GlyphWidth::Half);
pub const ARROW_DOWN_DISABLED: Grapheme = Grapheme::new_unchecked("▽", GlyphWidth::Half);
pub const ARROW_LEFT: Grapheme = Grapheme::new_unchecked("◀", GlyphWidth::Half);
pub const ARROW_LEFT_DISABLED: Grapheme = Grapheme::new_unchecked("◁", GlyphWidth::Half);
pub const BAR_MARKER_H: Grapheme = Grapheme::new_unchecked("━", GlyphWidth::Half);
pub const ARROW_RIGHT: Grapheme = Grapheme::new_unchecked("▶", GlyphWidth::Half);
pub const ARROW_RIGHT_DISABLED: Grapheme = Grapheme::new_unchecked("▷", GlyphWidth::Half);
pub fn set_content(rc: &Rc<RefCell<Self>>, mut content: Option<&Rc<RefCell<dyn Scrollable>>>) {
if let Some(old) = rc.borrow_mut().content.as_mut() {
old.borrow_mut().set_scroll_viewer(Weak::new());
}
if let Some(cnt) = content.as_mut() {
cnt.borrow_mut().set_scroll_viewer(Rc::downgrade(rc));
}
let brw = &mut rc.borrow_mut();
brw.content = content.cloned();
brw.provoke_changed_property(WindowProperty::Children);
}
pub fn set_scrollbar_visibility(&mut self, show: bool) {
self.show_scrollbar = show;
self.reevaluate_showbars();
}
fn reevaluate_showbars(&mut self) {
if let Some(cnt) = self.content.as_ref() {
let brw = cnt.borrow();
let old = self.draw_bars;
self.draw_bars = Vector2D::new(
self.size.x > 2 && brw.is_scrollable(Orientation::Vertical) && self.show_scrollbar,
self.size.y > 2
&& brw.is_scrollable(Orientation::Horizontal)
&& self.show_scrollbar,
);
if old != self.draw_bars {
self.provoke_changed_property(WindowProperty::Children);
}
}
}
fn render_scroller(
&self,
array: &mut dyn GlyphArray,
size: TPoint,
content: &dyn Scrollable,
orientation: Orientation,
other_bar: bool,
) {
let (arrow_prev, arrow_next, bar_marker);
let (arrow_prev_dis, arrow_next_dis);
let max_size;
match orientation {
Orientation::Horizontal => {
arrow_prev = Self::ARROW_LEFT;
arrow_prev_dis = Self::ARROW_LEFT_DISABLED;
arrow_next = Self::ARROW_RIGHT;
arrow_next_dis = Self::ARROW_RIGHT_DISABLED;
bar_marker = Self::BAR_MARKER_H;
max_size = size.x;
}
Orientation::Vertical => {
arrow_prev = Self::ARROW_UP;
arrow_prev_dis = Self::ARROW_UP_DISABLED;
arrow_next = Self::ARROW_DOWN;
arrow_next_dis = Self::ARROW_DOWN_DISABLED;
bar_marker = Self::BAR_MARKER_V;
max_size = size.y;
}
}
if max_size > 2 && content.is_scrollable(orientation) {
let offset = content.offset(orientation);
let viewport = content.viewport(orientation);
let mut arrow_up = array.get(TSize::MIN).unwrap();
if !self.base.enabled || offset == usize::MIN {
arrow_up.set_grapheme(arrow_prev_dis).ok();
if let Some(color) = self.base.colors.disabled {
arrow_up.set_fg(color);
}
}
else {
arrow_up.set_grapheme(arrow_prev).ok();
}
std::mem::drop(arrow_up);
let render_size = max_size - other_bar as TSize;
if max_size >= 2 {
let length = size[orientation as usize] - 2 - other_bar as TSize;
let start = (offset * length as usize + 1).div_ceil(viewport) as TSize;
let end = if offset + render_size as usize > viewport {
length + 1
}
else {
((offset + render_size as usize + 1) * length as usize).div_ceil(viewport)
as TSize
};
for idx in start..end + (start == end) as TSize {
let mut g = array.get(idx).unwrap();
g.set_grapheme(bar_marker).ok();
if let Some(color) = self.base.colors.disabled
&& !self.base.enabled
{
g.set_fg(color);
}
}
}
let mut arrow_down = array.get(max_size - 1 - other_bar as TSize).unwrap();
if !self.base.enabled || offset + render_size as usize >= viewport {
arrow_down.set_grapheme(arrow_next_dis).ok();
if let Some(color) = self.base.colors.disabled {
arrow_down.set_fg(color);
}
}
else {
arrow_down.set_grapheme(arrow_next).ok();
}
}
}
pub fn is_scrollable(&self, axis: Orientation) -> bool {
match self.content.as_ref() {
Some(sc) => sc.borrow().is_scrollable(axis),
None => false,
}
}
pub fn scroll(&mut self, direction: Direction, kind: ScrollKind) {
if let Some(sc) = self.content.as_ref() {
sc.borrow_mut().scroll(direction, kind);
}
}
pub fn set_offset(&mut self, direction: Orientation, offset: usize) {
if let Some(sc) = self.content.as_ref() {
sc.borrow_mut().set_offset(direction, offset);
}
}
pub fn offset(&self, axis: Orientation) -> usize {
match self.content.as_ref() {
Some(sc) => sc.borrow().offset(axis),
None => usize::MIN,
}
}
pub fn viewport(&self, axis: Orientation) -> usize {
match self.content.as_ref() {
Some(sc) => sc.borrow().viewport(axis),
None => usize::MIN,
}
}
}
impl Window for ScrollViewer {
fn render(&self, canvas: &mut crate::canvas::Canvas) {
if let Some(content) = self.content.as_ref() {
let brw = content.borrow();
let size = canvas.size();
if self.draw_bars.x {
self.render_scroller(
&mut canvas.get_column(size.x - 1, GlyphWidth::Half).unwrap(),
size,
&*brw,
Orientation::Vertical,
self.draw_bars.y,
);
}
if self.draw_bars.y {
self.render_scroller(
&mut canvas.get_row(size.y - 1, GlyphWidth::Half).unwrap(),
size,
&*brw,
Orientation::Horizontal,
self.draw_bars.x,
);
}
}
}
fn handle_event(&mut self, event: &mut WindowEvent) {
if let Event::Resize(w, h) = event.raw() {
self.size = Vector2D::new(*w, *h);
self.reevaluate_showbars();
}
}
fn children(&mut self, mut builder: SubWindowBuilder) -> super::SubWindows {
if let Some(ch) = self.content.clone() {
let rect = builder.base_rect();
builder
.add_pair(
ch,
Some(
rect.subrect(
0,
0,
rect.size.x - self.draw_bars.x as TSize,
rect.size.y - self.draw_bars.y as TSize,
)
.unwrap(),
),
)
.unwrap();
}
builder.build().unwrap()
}
fn focus(&self) -> Option<WindowRef> {
self.content.clone().map(|x| x as Rc<RefCell<dyn Window>>)
}
fn is_enabled(&self) -> bool {
self.base.enabled
}
}
impl HasWindowUID for ScrollViewer {
fn uid(&self) -> super::WindowUID {
self.base.uid
}
}
impl WindowLayout for ScrollViewer {
fn desired_size(&self, available_size: TPoint) -> TPoint {
self.base.desired_size(available_size)
}
fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
self.base.alignment
}
fn border(&self) -> BorderStyle {
self.base.border
}
fn is_visible(&self) -> bool {
self.base.visibility
}
fn margin(&self) -> Thickness {
self.base.margin
}
}
impl Widget for ScrollViewer {
fn set_visibility(&mut self, visibility: bool) {
self.base.visibility = visibility;
self.provoke_changed_property(WindowProperty::IsVisible);
}
fn set_width(&mut self, width: Size) {
self.base.size.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height(&mut self, height: Size) {
self.base.size.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_margin(&mut self, margin: Thickness) {
self.base.margin = margin;
self.provoke_changed_property(WindowProperty::Margin);
}
fn set_border(&mut self, border: BorderStyle) {
self.base.border = border;
self.provoke_changed_property(WindowProperty::Border);
}
fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
self.base.alignment = (horizontal, vertical);
self.provoke_changed_property(WindowProperty::Alignment);
}
fn set_enabled_state(&mut self, is_enabled: bool) {
self.base.enabled = is_enabled;
self.provoke_changed_property(WindowProperty::Children);
}
fn set_width_constraint(&mut self, width: SizeConstraint) {
self.base.constraints.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height_constraint(&mut self, height: SizeConstraint) {
self.base.constraints.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
}
impl WidgetColors for ScrollViewer {
fn set_disabled_color(&mut self, color: Option<Color>) {
self.base.colors.disabled = color;
}
fn set_base_fg_color(&mut self, color: Option<Color>) {
self.base.colors.base_fg = color;
}
fn set_base_bg_color(&mut self, color: Option<Color>) {
self.base.colors.base_bg = color;
}
}