use std::fmt::Debug;
use super::ScrollBar;
use kas::draw::{ClipRegion, TextClass};
use kas::event::NavKey;
use kas::event::ScrollDelta::{LineDelta, PixelDelta};
use kas::prelude::*;
#[widget(config=noauto)]
#[handler(send=noauto, msg = <W as event::Handler>::Msg)]
#[derive(Clone, Debug, Default, Widget)]
pub struct ScrollRegion<W: Widget> {
#[widget_core]
core: CoreData,
min_child_size: Size,
inner_size: Size,
max_offset: Coord,
offset: Coord,
scroll_rate: f32,
bar_width: u32,
auto_bars: bool,
show_bars: (bool, bool),
#[widget]
horiz_bar: ScrollBar<kas::Right>,
#[widget]
vert_bar: ScrollBar<kas::Down>,
#[widget]
inner: W,
}
impl<W: Widget> ScrollRegion<W> {
#[inline]
pub fn new(inner: W) -> Self {
ScrollRegion {
core: Default::default(),
min_child_size: Size::ZERO,
inner_size: Size::ZERO,
max_offset: Coord::ZERO,
offset: Coord::ZERO,
scroll_rate: 30.0,
bar_width: 0,
auto_bars: false,
show_bars: (false, false),
horiz_bar: ScrollBar::new(),
vert_bar: ScrollBar::new(),
inner,
}
}
#[inline]
pub fn with_auto_bars(mut self, enable: bool) -> Self {
self.auto_bars = enable;
self
}
#[inline]
pub fn with_bars(mut self, horiz: bool, vert: bool) -> Self {
self.show_bars = (horiz, vert);
self
}
#[inline]
pub fn show_bars(&mut self, horiz: bool, vert: bool) {
self.show_bars = (horiz, vert);
}
#[inline]
pub fn inner(&self) -> &W {
&self.inner
}
#[inline]
pub fn inner_mut(&mut self) -> &mut W {
&mut self.inner
}
#[inline]
pub fn max_offset(&self) -> Coord {
self.max_offset
}
#[inline]
pub fn offset(&self) -> Coord {
self.offset
}
#[inline]
pub fn set_offset(&mut self, offset: Coord) -> TkAction {
let offset = offset.clamp(Coord::ZERO, self.max_offset);
if offset == self.offset {
TkAction::None
} else {
self.offset = offset;
TkAction::RegionMoved
}
}
}
impl<W: Widget> WidgetConfig for ScrollRegion<W> {
fn configure(&mut self, mgr: &mut Manager) {
mgr.register_nav_fallback(self.id());
}
}
impl<W: Widget> Layout for ScrollRegion<W> {
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules {
let mut rules = self.inner.size_rules(size_handle, axis);
if axis.is_horizontal() {
self.min_child_size.0 = rules.min_size();
} else {
self.min_child_size.1 = rules.min_size();
}
let line_height = size_handle.line_height(TextClass::Label);
self.scroll_rate = 3.0 * line_height as f32;
rules.reduce_min_to(line_height);
self.bar_width = (size_handle.scrollbar().0).1;
if axis.is_horizontal() && (self.auto_bars || self.show_bars.1) {
rules.append(self.vert_bar.size_rules(size_handle, axis));
} else if axis.is_vertical() && (self.auto_bars || self.show_bars.0) {
rules.append(self.horiz_bar.size_rules(size_handle, axis));
}
rules
}
fn set_rect(&mut self, rect: Rect, _: AlignHints) {
self.core.rect = rect;
let pos = rect.pos;
self.inner_size = rect.size;
if self.auto_bars {
self.show_bars = (
self.min_child_size.0 + self.bar_width > rect.size.0,
self.min_child_size.1 + self.bar_width > rect.size.1,
);
}
if self.show_bars.0 {
self.inner_size.1 -= self.bar_width;
}
if self.show_bars.1 {
self.inner_size.0 -= self.bar_width;
}
let child_size = self.inner_size.max(self.min_child_size);
let child_rect = Rect::new(pos, child_size);
self.inner.set_rect(child_rect, AlignHints::NONE);
self.max_offset = Coord::from(child_size) - Coord::from(self.inner_size);
self.offset = self.offset.clamp(Coord::ZERO, self.max_offset);
if self.show_bars.0 {
let pos = Coord(pos.0, pos.1 + self.inner_size.1 as i32);
let size = Size(self.inner_size.0, self.bar_width);
self.horiz_bar
.set_rect(Rect { pos, size }, AlignHints::NONE);
let _ = self
.horiz_bar
.set_limits(self.max_offset.0 as u32, rect.size.0);
}
if self.show_bars.1 {
let pos = Coord(pos.0 + self.inner_size.0 as i32, pos.1);
let size = Size(self.bar_width, self.core.rect.size.1);
self.vert_bar.set_rect(Rect { pos, size }, AlignHints::NONE);
let _ = self
.vert_bar
.set_limits(self.max_offset.1 as u32, rect.size.1);
}
}
#[inline]
fn translation(&self, child_index: usize) -> Coord {
match child_index {
2 => self.offset,
_ => Coord::ZERO,
}
}
fn find_id(&self, coord: Coord) -> Option<WidgetId> {
if !self.rect().contains(coord) {
return None;
}
self.horiz_bar
.find_id(coord)
.or_else(|| self.vert_bar.find_id(coord))
.or_else(|| self.inner.find_id(coord + self.offset))
.or(Some(self.id()))
}
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
let disabled = disabled || self.is_disabled();
if self.show_bars.0 {
self.horiz_bar.draw(draw_handle, mgr, disabled);
}
if self.show_bars.1 {
self.vert_bar.draw(draw_handle, mgr, disabled);
}
let rect = Rect {
pos: self.core.rect.pos,
size: self.inner_size,
};
draw_handle.clip_region(rect, self.offset, ClipRegion::Scroll, &mut |handle| {
self.inner.draw(handle, mgr, disabled)
});
}
}
impl<W: Widget> event::SendEvent for ScrollRegion<W> {
fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response<Self::Msg> {
if self.is_disabled() {
return Response::Unhandled(event);
}
let event = if id <= self.horiz_bar.id() {
match Response::<Self::Msg>::try_from(self.horiz_bar.send(mgr, id, event)) {
Ok(Response::Unhandled(event)) => event,
Ok(r) => return r,
Err(msg) => {
*mgr += self.set_offset(Coord(msg as i32, self.offset.1));
return Response::None;
}
}
} else if id <= self.vert_bar.id() {
match Response::<Self::Msg>::try_from(self.vert_bar.send(mgr, id, event)) {
Ok(Response::Unhandled(event)) => event,
Ok(r) => return r,
Err(msg) => {
*mgr += self.set_offset(Coord(self.offset.0, msg as i32));
return Response::None;
}
}
} else if id <= self.inner.id() {
let event = match event {
Event::PressStart {
source,
start_id,
coord,
} => Event::PressStart {
source,
start_id,
coord: coord + self.offset,
},
Event::PressMove {
source,
cur_id,
coord,
delta,
} => Event::PressMove {
source,
cur_id,
coord: coord + self.offset,
delta,
},
Event::PressEnd {
source,
end_id,
coord,
} => Event::PressEnd {
source,
end_id,
coord: coord + self.offset,
},
event => event,
};
match self.inner.send(mgr, id, event) {
Response::Unhandled(event) => event,
Response::Focus(rect) => {
let mut offset = self.offset;
offset = offset.max(rect.pos_end() - self.core.rect.pos_end());
offset = offset.min(rect.pos - self.core.rect.pos);
*mgr += self.set_offset(offset);
return Response::Focus(rect - self.offset);
}
r => return r,
}
} else {
event
};
let scroll = |w: &mut Self, mgr: &mut Manager, delta| {
let d = match delta {
LineDelta(x, y) => Coord((-w.scroll_rate * x) as i32, (w.scroll_rate * y) as i32),
PixelDelta(d) => d,
};
let action = w.set_offset(w.offset - d);
if action != TkAction::None {
*mgr += action
+ w.horiz_bar.set_value(w.offset.0 as u32)
+ w.vert_bar.set_value(w.offset.1 as u32);
Response::None
} else {
Response::Unhandled(Event::Scroll(delta))
}
};
match event {
Event::NavKey(key) => {
let delta = match key {
NavKey::Left => LineDelta(-1.0, 0.0),
NavKey::Right => LineDelta(1.0, 0.0),
NavKey::Up => LineDelta(0.0, 1.0),
NavKey::Down => LineDelta(0.0, -1.0),
NavKey::Home | NavKey::End => {
let action = self.set_offset(match key {
NavKey::Home => Coord::ZERO,
_ => self.max_offset,
});
if action != TkAction::None {
*mgr += action
+ self.horiz_bar.set_value(self.offset.0 as u32)
+ self.vert_bar.set_value(self.offset.1 as u32);
}
return Response::None;
}
NavKey::PageUp => PixelDelta(Coord(0, self.core.rect.size.1 as i32 / 2)),
NavKey::PageDown => PixelDelta(Coord(0, -(self.core.rect.size.1 as i32 / 2))),
};
scroll(self, mgr, delta)
}
Event::Scroll(delta) => scroll(self, mgr, delta),
Event::PressStart { source, coord, .. } if source.is_primary() => {
mgr.request_grab(
self.id(),
source,
coord,
event::GrabMode::Grab,
Some(event::CursorIcon::Grabbing),
);
Response::None
}
Event::PressMove { delta, .. } => {
let action = self.set_offset(self.offset - delta);
if action != TkAction::None {
*mgr += action
+ self.horiz_bar.set_value(self.offset.0 as u32)
+ self.vert_bar.set_value(self.offset.1 as u32);
}
Response::None
}
Event::PressEnd { .. } => {
Response::None
}
e @ _ => Response::Unhandled(e),
}
}
}