use super::*;
use crate::canvas::*;
use std::{
cell::RefCell,
rc::{Rc, Weak},
};
use crossterm::event::{KeyCode, KeyModifiers};
pub type StackpanelSelectionChangedCallback = dyn Fn(&mut Stackpanel);
pub type StackpanelKeyHandler = fn(&mut Stackpanel, event: &mut WindowEvent);
#[derive(Clone)]
struct Child {
rc: WindowRef,
offset: usize,
len: usize,
}
impl Child {
fn new(rc: WindowRef) -> Self {
Self {
rc,
offset: usize::MIN,
len: usize::MIN,
}
}
}
struct Children {
items: Vec<Child>,
focus: usize,
alignment: Alignment,
}
impl Default for Children {
fn default() -> Self {
Self {
items: Vec::default(),
focus: usize::MIN,
alignment: Alignment::Center,
}
}
}
pub struct Stackpanel {
orientation: Orientation,
margin: u8,
children: Children,
base: WidgetBase,
scroll: ScrollBase,
callback: Rc<StackpanelSelectionChangedCallback>,
key_handler: StackpanelKeyHandler,
}
impl Default for Stackpanel {
fn default() -> Self {
Self::new(Orientation::Vertical)
}
}
impl Stackpanel {
pub fn default_callback(_: &mut Self) {}
pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
if let Event::Key(k) = event.raw() {
let scroll_mode =
k.modifiers.contains(KeyModifiers::CONTROL) && self.has_scroll_viewer();
match self.orientation {
Orientation::Horizontal => match k.code {
KeyCode::Left => {
self.scroll(Direction::Left, ScrollKind::Line, scroll_mode);
event.handled = true;
}
KeyCode::Right => {
self.scroll(Direction::Right, ScrollKind::Line, scroll_mode);
event.handled = true;
}
KeyCode::PageUp => {
self.scroll(Direction::Left, ScrollKind::Page, scroll_mode);
event.handled = true;
}
KeyCode::PageDown => {
self.scroll(Direction::Right, ScrollKind::Page, scroll_mode);
event.handled = true;
}
_ => {}
},
Orientation::Vertical => match k.code {
KeyCode::Up => {
self.scroll(Direction::Up, ScrollKind::Line, scroll_mode);
event.handled = true;
}
KeyCode::Down => {
self.scroll(Direction::Down, ScrollKind::Line, scroll_mode);
event.handled = true;
}
KeyCode::PageUp => {
self.scroll(Direction::Up, ScrollKind::Page, scroll_mode);
event.handled = true;
}
KeyCode::PageDown => {
self.scroll(Direction::Down, ScrollKind::Page, scroll_mode);
event.handled = true;
}
_ => {}
},
}
}
}
pub fn new(orientation: Orientation) -> Self {
Self {
orientation,
margin: 1,
children: Children::default(),
base: WidgetBase::default(),
scroll: ScrollBase::default(),
callback: Rc::new(Self::default_callback),
key_handler: Self::default_key_handler,
}
}
pub fn set_selection_changed_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
self.callback = Rc::new(callback);
}
pub fn set_key_handler(&mut self, f: StackpanelKeyHandler) {
self.key_handler = f;
}
pub fn set_item_alignment(&mut self, alignment: Alignment) {
self.children.alignment = alignment;
self.provoke_changed_property(WindowProperty::Children);
}
pub fn set_orientation(&mut self, orientation: Orientation) {
self.orientation = orientation;
self.provoke_changed_property(WindowProperty::Children);
}
pub fn set_focus(&mut self, item: &Rc<RefCell<dyn Window>>) {
let uid = item.borrow().uid();
self.children.focus = self
.children
.items
.iter()
.position(|x| x.rc.borrow().uid() == uid)
.unwrap_or_default();
self.provoke_changed_property(WindowProperty::Focus);
}
pub fn set_child_margin(&mut self, margin: u8) {
self.margin = margin;
}
fn is_selection_visible(&self) -> bool {
let idx = self.orientation as usize;
let focus = &self.children.items[self.children.focus];
focus.offset >= self.scroll.offset[idx]
&& (focus.offset + focus.len)
< (self.scroll.offset[idx] + self.scroll.render_size[idx] as usize)
}
fn make_selection_visible(&mut self) {
let child = &self.children.items[self.children.focus];
let render_size = self.scroll.render_size[self.orientation as usize];
if let Some(new_offset) = self
.children
.items
.iter()
.map(|x| x.offset)
.find(|x| x + render_size as usize >= child.offset + child.len)
{
self.scroll.set_offset(self.orientation, new_offset);
self.provoke_changed_property(WindowProperty::Children);
}
}
pub fn scroll(&mut self, direction: Direction, kind: ScrollKind, scroll_only: bool) {
match kind {
ScrollKind::Line => match direction {
Direction::Left | Direction::Up => self.move_previous(scroll_only),
Direction::Right | Direction::Down => self.move_next(scroll_only),
},
ScrollKind::Page => match scroll_only {
true => self.scroll.scroll(direction, kind),
false => {
let delta = self.scroll.render_size[self.orientation as usize] as usize;
let min_offset = match direction {
Direction::Left | Direction::Up => self.scroll.offset
[self.orientation as usize]
.checked_sub(delta)
.unwrap_or_default(),
Direction::Right | Direction::Down => {
self.scroll.offset[self.orientation as usize] + delta
}
};
if let Some(index) = self
.children
.items
.iter()
.position(|x| x.offset >= min_offset)
{
self.children.focus = index;
self.provoke_changed_property(WindowProperty::Focus);
self.selection_changed();
self.scroll.set_offset(
self.orientation,
self.children.items[self.children.focus].offset,
);
self.provoke_changed_property(WindowProperty::Children);
}
}
},
}
}
fn move_next(&mut self, scroll_only: bool) {
match scroll_only {
true => {
if let Some(new_offset) = self
.children
.items
.iter()
.map(|x| x.offset)
.find(|x| *x > self.scroll.offset[self.orientation as usize])
{
self.scroll.set_offset(self.orientation, new_offset);
self.provoke_changed_property(WindowProperty::Children);
}
}
false => {
if self.children.focus < self.children.items.len() - 1 {
if let Some(idx) = self.children.items[self.children.focus + 1..]
.iter()
.position(|x| x.rc.borrow_mut().is_enabled())
{
if !self.is_selection_visible() {
self.make_selection_visible();
}
self.children.focus = self.children.focus + 1 + idx;
self.provoke_changed_property(WindowProperty::Focus);
self.selection_changed();
if self.has_scroll_viewer() {
let idx = self.orientation as usize;
let max =
self.scroll.offset[idx] + self.scroll.render_size[idx] as usize;
let child = &self.children.items[self.children.focus];
if child.offset >= max || child.offset + child.len >= max {
let render_size =
self.scroll.render_size[self.orientation as usize];
if let Some(new_offset) =
self.children.items.iter().map(|x| x.offset).find(|x| {
x + render_size as usize >= child.offset + child.len
}) {
self.scroll.set_offset(self.orientation, new_offset);
self.provoke_changed_property(WindowProperty::Children);
}
}
}
}
}
}
}
}
fn move_previous(&mut self, scroll_only: bool) {
match scroll_only {
true => {
if let Some(new_offset) = self
.children
.items
.iter()
.rev()
.map(|x| x.offset)
.find(|x| *x < self.scroll.offset[self.orientation as usize])
{
self.scroll.set_offset(self.orientation, new_offset);
self.provoke_changed_property(WindowProperty::Children);
}
}
false => {
if self.children.focus > usize::MIN {
if let Some(idx) = self.children.items[..self.children.focus]
.iter()
.rev()
.position(|x| x.rc.borrow_mut().is_enabled())
{
if !self.is_selection_visible() {
self.make_selection_visible();
}
self.children.focus = (self.children.focus - 1) - idx;
self.provoke_changed_property(WindowProperty::Focus);
self.selection_changed();
if self.has_scroll_viewer() {
let idx = self.orientation as usize;
let min = self.scroll.offset[idx];
let offset = self.children.items[self.children.focus].offset;
if offset < min {
self.scroll.set_offset(self.orientation, offset);
self.provoke_changed_property(WindowProperty::Children);
}
}
}
}
}
}
}
pub fn has_scroll_viewer(&self) -> bool {
self.scroll.scroll_viewer.strong_count() > usize::MIN
}
fn retrieve_children(&mut self, builder: &mut SubWindowBuilder) {
let has_sv = self.has_scroll_viewer();
let base_rect = builder.base_rect();
self.scroll.viewport = Default::default();
self.scroll.render_size = base_rect.size;
let full_size = match self.orientation {
Orientation::Horizontal => base_rect
.size
.x
.checked_sub(self.children.items.len() as TSize - 1)
.unwrap_or(base_rect.size.x),
Orientation::Vertical => base_rect
.size
.y
.checked_sub(self.children.items.len() as TSize - 1)
.unwrap_or(base_rect.size.y),
};
let fixed_size = match full_size
.checked_div(self.children.items.len() as TSize)
.unwrap_or_default()
{
TSize::MIN => 1,
v => v,
};
let item_count = self.children.items.len();
match self.orientation {
Orientation::Horizontal => {
for (n, item) in self.children.items.iter_mut().enumerate() {
let brw = item.rc.borrow();
let tn = brw.border().thickness();
let size = brw.desired_size(Vector2D::new(base_rect.size.x, base_rect.size.y));
let len = match has_sv {
true => size.x + tn.width(),
false => fixed_size,
} as usize;
let start = match self.children.alignment {
Alignment::LowerBound => TSize::MIN,
Alignment::Center => (base_rect.size.y - size.y) / 2,
Alignment::HigherBound => base_rect.size.y - size.y,
};
if self.scroll.viewport.x >= self.scroll.offset.x {
match base_rect.subrect(
(self.scroll.viewport.x - self.scroll.offset.x) as TSize,
start,
len as TSize,
size.y,
) {
Ok(rect) => {
builder.add_rect(rect).unwrap();
builder.add_child(item.rc.clone());
}
Err(_) => {
if !has_sv {
self.scroll.viewport.x += len;
break;
}
}
}
}
item.offset = self.scroll.viewport.y;
item.len = len;
self.scroll.viewport.x += len;
if n < item_count - 1 {
self.scroll.viewport.x += self.margin as usize;
}
}
self.scroll.viewport.y = base_rect.size.y as usize;
}
Orientation::Vertical => {
for (n, item) in self.children.items.iter_mut().enumerate() {
let brw = item.rc.borrow();
let tn = brw.border().thickness();
let size = brw.desired_size(Vector2D::new(base_rect.size.x, base_rect.size.y));
let len = match has_sv {
true => size.y + tn.height(),
false => fixed_size,
} as usize;
let start = match self.children.alignment {
Alignment::LowerBound => TSize::MIN,
Alignment::Center => (base_rect.size.x - size.x) / 2,
Alignment::HigherBound => base_rect.size.x - size.x,
};
if self.scroll.viewport.y >= self.scroll.offset.y {
match base_rect.subrect(
start,
(self.scroll.viewport.y - self.scroll.offset.y) as TSize,
size.x,
len as TSize,
) {
Ok(rect) => {
builder.add_rect(rect).unwrap();
builder.add_child(item.rc.clone());
}
Err(_) => {
if !has_sv {
self.scroll.viewport.y += len;
break;
}
}
}
}
item.offset = self.scroll.viewport.y;
item.len = len;
self.scroll.viewport.y += len;
if n < item_count - 1 {
self.scroll.viewport.y += self.margin as usize;
}
}
self.scroll.viewport.x = base_rect.size.x as usize;
}
}
}
fn selection_changed(&mut self) {
let cb = self.callback.clone();
cb(self);
}
}
impl Scrollable for Stackpanel {
fn is_scrollable(&self, axis: Orientation) -> bool {
self.scroll.is_scrollable(axis)
}
fn scroll(&mut self, direction: Direction, kind: ScrollKind) {
self.scroll(direction, kind, false);
}
fn set_offset(&mut self, direction: Orientation, offset: usize) {
self.scroll.set_offset(direction, offset);
}
fn offset(&self, axis: Orientation) -> usize {
self.scroll.offset[axis as usize]
}
fn viewport(&self, axis: Orientation) -> usize {
self.scroll.viewport[axis as usize]
}
fn set_scroll_viewer(&mut self, scroll_viewer: Weak<RefCell<ScrollViewer>>) {
self.scroll.set_scroll_viewer(scroll_viewer);
self.provoke_changed_property(WindowProperty::Children);
}
}
impl ItemCollection<WindowRef> for Stackpanel {
type Index = usize;
fn add_item(&mut self, item: WindowRef) {
self.children.items.push(Child::new(item));
self.provoke_changed_property(WindowProperty::Children);
}
fn remove_item(&mut self, item: &WindowRef) {
let uid = item.borrow().uid();
if let Some(index) = self
.children
.items
.iter()
.position(|x| x.rc.borrow().uid() == uid)
{
self.children.items.remove(index);
if self.children.focus == index {
self.children.focus = usize::MIN;
self.selection_changed();
self.provoke_changed_property(WindowProperty::Focus);
}
self.provoke_changed_property(WindowProperty::Children);
}
}
fn remove_at(&mut self, index: Self::Index) -> WindowRef {
self.children.items.remove(index).rc
}
fn get_item(&mut self, index: Self::Index) -> Option<&WindowRef> {
match self.children.items.get(index) {
Some(item) => Some(&item.rc),
None => None,
}
}
fn clear_items(&mut self) {
if !self.children.items.is_empty() {
self.children.items.clear();
self.provoke_changed_property(WindowProperty::Children);
self.provoke_changed_property(WindowProperty::Focus);
self.selection_changed();
}
}
fn items(&self) -> Self::Index {
self.children.items.len()
}
}
impl Window for Stackpanel {
fn render(&self, canvas: &mut Canvas) {
if let Some(color) = self.base.colors.disabled
&& !self.base.enabled
{
canvas.fill_foreground(color);
}
}
fn children(&mut self, mut builder: SubWindowBuilder) -> SubWindows {
if self.children.items.is_empty() {
return SubWindows::default();
}
builder.disable_overlapping();
self.retrieve_children(&mut builder);
builder.build().unwrap()
}
fn handle_event(&mut self, event: &mut WindowEvent) {
(self.key_handler)(self, event);
}
fn focus(&self) -> Option<WindowRef> {
match self.base.enabled {
true => self
.children
.items
.get(self.children.focus)
.cloned()
.map(|x| x.rc),
false => None,
}
}
fn is_enabled(&self) -> bool {
self.base.enabled
}
}
impl WindowLayout for Stackpanel {
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 HasWindowUID for Stackpanel {
fn uid(&self) -> WindowUID {
self.base.uid
}
}
impl Widget for Stackpanel {
fn set_visibility(&mut self, visibility: bool) {
self.provoke_changed_property(WindowProperty::IsVisible);
self.base.visibility = visibility;
}
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 Stackpanel {
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;
}
}