use crate::core::{Color, Font, Point, Rect, Size};
use crate::event::{Event, EventHandler};
use crate::render::RenderContext;
use crate::signal::Signal1;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
pub struct TileView {
base: BaseWidget,
page_count: u32,
current_page: u32,
pub page_changed: Signal1<u32>,
}
impl TileView {
pub fn new(rect: Rect) -> Self {
Self {
base: BaseWidget::new(WidgetKind::TileView, rect, "TileView"),
page_count: 1,
current_page: 0,
page_changed: Signal1::new(),
}
}
pub fn page_count(&self) -> u32 {
self.page_count
}
pub fn set_page_count(&mut self, count: u32) {
let count = count.max(1);
self.page_count = count;
if self.current_page >= self.page_count {
self.current_page = self.page_count.saturating_sub(1);
}
}
pub fn current_page(&self) -> u32 {
self.current_page
}
pub fn set_current_page(&mut self, page: u32) {
let clamped = page.min(self.page_count.saturating_sub(1));
if self.current_page == clamped {
return;
}
self.current_page = clamped;
self.page_changed.emit(self.current_page);
self.base.changed.emit();
}
fn next_page(&mut self) {
if self.page_count > 1 {
self.set_current_page(self.current_page + 1);
}
}
fn prev_page(&mut self) {
if self.current_page > 0 {
self.set_current_page(self.current_page - 1);
}
}
}
impl Widget for TileView {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
fn size_hint(&self) -> Size {
Size::new(200, 200)
}
}
impl EventHandler for TileView {
fn handle_event(&mut self, event: &Event) {
self.base.handle_event(event);
if let Event::KeyDown((key, _modifiers)) = event {
match *key {
37 => {
self.prev_page();
}
39 => {
self.next_page();
}
_ => {}
}
}
}
}
impl Draw for TileView {
fn draw(&mut self, context: &mut RenderContext) {
let rect = self.geometry();
if rect.width == 0 || rect.height == 0 {
return;
}
let bg = self.style().background_color.unwrap_or(Color::WHITE);
context.fill_rect(rect, bg);
let border = self.style().border_color.unwrap_or(Color::from_rgb(200, 200, 200));
context.draw_rect_stroke(rect, border, 1);
let text = format!("Page {}", self.current_page + 1);
let font = Font::default();
let metrics = context.measure_text(&text, &font);
let text_color = self.style().text_color.unwrap_or(Color::from_rgb(60, 60, 60));
let cx = rect.x + rect.width as i32 / 2;
let cy = rect.y + rect.height as i32 / 2;
let text_x = cx - (metrics.width as i32 / 2);
let text_y = cy - (metrics.height as i32 / 2);
context.draw_text(Point::new(text_x, text_y), &text, &font, text_color);
let dot_count = self.page_count;
if dot_count > 0 {
let dot_radius: u32 = 3;
let dot_spacing: i32 = 12;
let dots_total_width = (dot_count as i32 - 1) * dot_spacing;
let start_x = rect.x + (rect.width as i32 - dots_total_width) / 2;
let dots_y = rect.y + rect.height as i32 - 14_i32;
for i in 0..dot_count {
let dx = start_x + (i as i32) * dot_spacing;
let center = Point::new(dx, dots_y);
if i == self.current_page {
let dot_color = self.style().text_color.unwrap_or(Color::from_rgb(0, 120, 215));
context.fill_circle(center, dot_radius, dot_color);
} else {
let dot_color =
self.style().border_color.unwrap_or(Color::from_rgb(180, 180, 180));
context.draw_circle_stroke(center, dot_radius, dot_color, 1);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Color, Rect, Size};
use crate::render::{PaintBackend, SoftwarePaintBackend};
#[test]
fn tile_view_creation() {
let tv = TileView::new(Rect::new(0, 0, 300, 400));
assert_eq!(tv.page_count(), 1);
assert_eq!(tv.current_page(), 0);
assert_eq!(tv.kind(), WidgetKind::TileView);
}
#[test]
fn tile_view_switch_page() {
let mut tv = TileView::new(Rect::new(0, 0, 300, 400));
tv.set_page_count(3);
assert_eq!(tv.page_count(), 3);
tv.set_current_page(1);
assert_eq!(tv.current_page(), 1);
tv.set_current_page(10);
assert_eq!(tv.current_page(), 2);
tv.set_current_page(0);
assert_eq!(tv.current_page(), 0);
}
#[test]
fn tile_view_page_count() {
let mut tv = TileView::new(Rect::new(0, 0, 300, 400));
tv.set_page_count(5);
assert_eq!(tv.page_count(), 5);
tv.set_page_count(0);
assert_eq!(tv.page_count(), 1);
tv.set_page_count(5);
tv.set_current_page(4);
tv.set_page_count(3);
assert_eq!(tv.current_page(), 2);
tv.set_page_count(1);
assert_eq!(tv.current_page(), 0);
}
#[test]
fn tile_view_draw_no_panic() {
let mut tv = TileView::new(Rect::new(0, 0, 300, 400));
tv.set_page_count(4);
tv.set_current_page(2);
let mut backend = SoftwarePaintBackend::new(Size::new(300, 400), 1.0);
backend.begin_frame(Color::WHITE);
let mut context = RenderContext::new(&mut backend);
tv.draw(&mut context);
backend.end_frame();
let rgba = backend.frame_rgba();
assert!(!rgba.is_empty());
}
#[test]
fn tile_view_draw_zero_geometry_no_panic() {
let mut tv = TileView::new(Rect::new(0, 0, 0, 0));
let mut backend = SoftwarePaintBackend::new(Size::new(10, 10), 1.0);
backend.begin_frame(Color::WHITE);
let mut context = RenderContext::new(&mut backend);
tv.draw(&mut context);
backend.end_frame();
}
#[test]
fn tile_view_keyboard_navigation() {
let mut tv = TileView::new(Rect::new(0, 0, 300, 400));
tv.set_page_count(3);
assert_eq!(tv.current_page(), 0);
tv.handle_event(&Event::KeyDown((39, 0)));
assert_eq!(tv.current_page(), 1);
tv.handle_event(&Event::KeyDown((39, 0)));
assert_eq!(tv.current_page(), 2);
tv.handle_event(&Event::KeyDown((39, 0)));
assert_eq!(tv.current_page(), 2);
tv.handle_event(&Event::KeyDown((37, 0)));
assert_eq!(tv.current_page(), 1);
tv.handle_event(&Event::KeyDown((37, 0)));
assert_eq!(tv.current_page(), 0);
tv.handle_event(&Event::KeyDown((37, 0)));
assert_eq!(tv.current_page(), 0);
}
#[test]
fn tile_view_page_changed_signal() {
let mut tv = TileView::new(Rect::new(0, 0, 300, 400));
tv.set_page_count(3);
let last_page = std::sync::Arc::new(std::sync::Mutex::new(0u32));
let lp = last_page.clone();
tv.page_changed.connect(move |p| {
*lp.lock().unwrap() = *p;
});
tv.set_current_page(1);
assert_eq!(*last_page.lock().unwrap(), 1);
tv.set_current_page(2);
assert_eq!(*last_page.lock().unwrap(), 2);
}
#[test]
fn tile_view_size_hint() {
let tv = TileView::new(Rect::new(0, 0, 100, 100));
assert_eq!(tv.size_hint(), Size::new(200, 200));
}
#[test]
fn tile_view_geometry_delegation() {
let mut tv = TileView::new(Rect::new(0, 0, 300, 400));
tv.set_geometry(Rect::new(10, 10, 200, 300));
assert_eq!(tv.geometry(), Rect::new(10, 10, 200, 300));
}
}