use crate::core::Size;
#[derive(Debug, Clone)]
pub struct PresentationController {
current_page: usize,
total_pages: usize,
transition_ms: u32,
show_indicator: bool,
}
impl PresentationController {
pub fn new(total_pages: usize) -> Self {
Self {
current_page: 0,
total_pages: total_pages.max(1),
transition_ms: 300,
show_indicator: true,
}
}
pub fn current_page(&self) -> usize {
self.current_page
}
pub fn total_pages(&self) -> usize {
self.total_pages
}
pub fn next_page(&mut self) -> bool {
if self.current_page + 1 < self.total_pages {
self.current_page += 1;
true
} else {
false
}
}
pub fn prev_page(&mut self) -> bool {
if self.current_page > 0 {
self.current_page -= 1;
true
} else {
false
}
}
pub fn go_to_page(&mut self, page: usize) {
self.current_page = page.min(self.total_pages.saturating_sub(1));
}
pub fn reset(&mut self) {
self.current_page = 0;
}
pub fn is_first(&self) -> bool {
self.current_page == 0
}
pub fn is_last(&self) -> bool {
self.current_page + 1 >= self.total_pages
}
pub fn set_total_pages(&mut self, total: usize) {
self.total_pages = total.max(1);
self.current_page = self.current_page.min(self.total_pages.saturating_sub(1));
}
pub fn transition_ms(&self) -> u32 {
self.transition_ms
}
pub fn set_transition_ms(&mut self, ms: u32) {
self.transition_ms = ms;
}
pub fn show_indicator(&self) -> bool {
self.show_indicator
}
pub fn set_show_indicator(&mut self, show: bool) {
self.show_indicator = show;
}
pub fn page_label(&self) -> String {
format!("{} / {}", self.current_page + 1, self.total_pages)
}
}
impl Default for PresentationController {
fn default() -> Self {
Self::new(1)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProjectionRenderConfig {
pub text_scale: f32,
pub read_only: bool,
pub full_screen: bool,
pub show_page_indicator: bool,
pub hide_chrome: bool,
pub indicator_height: u32,
}
impl ProjectionRenderConfig {
pub const fn new() -> Self {
Self {
text_scale: 1.2,
read_only: true,
full_screen: true,
show_page_indicator: true,
hide_chrome: true,
indicator_height: 0,
}
}
pub fn scale_font_size(&self, base_pt: f32) -> f32 {
base_pt * self.text_scale
}
pub fn scale_dimension(&self, base: u32) -> u32 {
(base as f32 * self.text_scale).round() as u32
}
pub fn indicator_bar_height(&self, screen_height: u32) -> u32 {
if self.indicator_height > 0 {
self.indicator_height
} else {
(screen_height as f32 * 0.04).round().max(24.0) as u32
}
}
pub fn with_full_screen(mut self, enabled: bool) -> Self {
self.full_screen = enabled;
self
}
pub fn with_page_indicator(mut self, show: bool) -> Self {
self.show_page_indicator = show;
self
}
pub fn with_text_scale(mut self, scale: f32) -> Self {
self.text_scale = scale.clamp(0.5, 3.0);
self
}
pub fn with_indicator_height(mut self, height: u32) -> Self {
self.indicator_height = height;
self
}
}
impl Default for ProjectionRenderConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ProjectionLayoutHelper {
pub screen_size: Size,
pub config: ProjectionRenderConfig,
}
impl ProjectionLayoutHelper {
pub fn new(screen_size: Size, config: ProjectionRenderConfig) -> Self {
Self { screen_size, config }
}
pub fn content_area(&self) -> (u32, u32, u32, u32) {
let indicator_h = if self.config.show_page_indicator {
self.config.indicator_bar_height(self.screen_size.height)
} else {
0
};
(0, 0, self.screen_size.width, self.screen_size.height.saturating_sub(indicator_h))
}
pub fn indicator_bar_rect(&self) -> (u32, u32, u32, u32) {
let indicator_h = if self.config.show_page_indicator {
self.config.indicator_bar_height(self.screen_size.height)
} else {
0
};
(
0,
self.screen_size.height.saturating_sub(indicator_h),
self.screen_size.width,
indicator_h,
)
}
pub fn scale(&self, base: u32) -> u32 {
self.config.scale_dimension(base)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presentation_controller_default() {
let ctrl = PresentationController::default();
assert_eq!(ctrl.current_page(), 0);
assert_eq!(ctrl.total_pages(), 1);
assert!(ctrl.is_first());
assert!(ctrl.is_last());
assert_eq!(ctrl.page_label(), "1 / 1");
}
#[test]
fn presentation_controller_navigation() {
let mut ctrl = PresentationController::new(5);
assert_eq!(ctrl.current_page(), 0);
assert!(ctrl.is_first());
assert!(!ctrl.is_last());
assert!(ctrl.next_page());
assert_eq!(ctrl.current_page(), 1);
assert!(!ctrl.is_first());
assert!(!ctrl.is_last());
assert!(ctrl.next_page());
assert_eq!(ctrl.current_page(), 2);
assert!(ctrl.prev_page());
assert_eq!(ctrl.current_page(), 1);
ctrl.go_to_page(4);
assert_eq!(ctrl.current_page(), 4);
assert!(ctrl.is_last());
assert!(!ctrl.next_page());
assert_eq!(ctrl.current_page(), 4);
ctrl.go_to_page(0);
assert!(!ctrl.prev_page());
assert_eq!(ctrl.current_page(), 0);
}
#[test]
fn presentation_controller_clamp() {
let mut ctrl = PresentationController::new(3);
ctrl.go_to_page(100);
assert_eq!(ctrl.current_page(), 2);
assert!(ctrl.is_last());
}
#[test]
fn presentation_controller_set_total_pages() {
let mut ctrl = PresentationController::new(10);
ctrl.go_to_page(9);
ctrl.set_total_pages(3);
assert_eq!(ctrl.current_page(), 2);
assert_eq!(ctrl.total_pages(), 3);
}
#[test]
fn presentation_controller_reset() {
let mut ctrl = PresentationController::new(10);
ctrl.go_to_page(5);
ctrl.reset();
assert_eq!(ctrl.current_page(), 0);
assert!(ctrl.is_first());
}
#[test]
fn presentation_controller_label() {
let ctrl = PresentationController::new(12);
assert_eq!(ctrl.page_label(), "1 / 12");
let mut ctrl = PresentationController::new(7);
ctrl.go_to_page(3);
assert_eq!(ctrl.page_label(), "4 / 7");
}
#[test]
fn projection_config_default() {
let cfg = ProjectionRenderConfig::new();
assert!((cfg.text_scale - 1.2).abs() < f32::EPSILON);
assert!(cfg.read_only);
assert!(cfg.full_screen);
assert!(cfg.show_page_indicator);
assert!(cfg.hide_chrome);
assert_eq!(cfg.indicator_height, 0);
}
#[test]
fn projection_config_scale_font() {
let cfg = ProjectionRenderConfig::new();
let scaled = cfg.scale_font_size(12.0);
assert!((scaled - 14.4).abs() < 1e-4, "expected ~14.4, got {}", scaled);
}
#[test]
fn projection_config_scale_dimension() {
let cfg = ProjectionRenderConfig::new();
assert_eq!(cfg.scale_dimension(100), 120);
}
#[test]
fn projection_config_indicator_bar_height() {
let cfg = ProjectionRenderConfig::new();
let h = cfg.indicator_bar_height(1080);
assert_eq!(h, 43); }
#[test]
fn projection_config_custom_indicator_height() {
let cfg = ProjectionRenderConfig::new().with_indicator_height(60);
assert_eq!(cfg.indicator_height, 60);
assert_eq!(cfg.indicator_bar_height(1080), 60);
}
#[test]
fn projection_config_with_full_screen() {
let cfg = ProjectionRenderConfig::new().with_full_screen(false);
assert!(!cfg.full_screen);
assert!(cfg.read_only);
}
#[test]
fn projection_config_with_text_scale_clamp() {
let cfg = ProjectionRenderConfig::new().with_text_scale(5.0);
assert!((cfg.text_scale - 3.0).abs() < f32::EPSILON);
let cfg = ProjectionRenderConfig::new().with_text_scale(0.1);
assert!((cfg.text_scale - 0.5).abs() < f32::EPSILON);
}
#[test]
fn projection_layout_helper_content_area() {
let size = Size::new(1920, 1080);
let cfg = ProjectionRenderConfig::new();
let helper = ProjectionLayoutHelper::new(size, cfg);
let (x, y, w, h) = helper.content_area();
assert_eq!(x, 0);
assert_eq!(y, 0);
assert_eq!(w, 1920);
assert_eq!(h, 1080 - 43); }
#[test]
fn projection_layout_helper_indicator_bar_rect() {
let size = Size::new(1920, 1080);
let cfg = ProjectionRenderConfig::new();
let helper = ProjectionLayoutHelper::new(size, cfg);
let (x, y, w, h) = helper.indicator_bar_rect();
assert_eq!(x, 0);
assert_eq!(y, 1080 - 43);
assert_eq!(w, 1920);
assert_eq!(h, 43);
}
#[test]
fn projection_hide_indicator_removes_bar() {
let size = Size::new(1920, 1080);
let cfg = ProjectionRenderConfig::new().with_page_indicator(false);
let helper = ProjectionLayoutHelper::new(size, cfg);
let (_x, _y, _w, h) = helper.indicator_bar_rect();
assert_eq!(h, 0);
let (_, _, _, ch) = helper.content_area();
assert_eq!(ch, 1080);
}
#[test]
fn projection_controller_transition_ms() {
let mut ctrl = PresentationController::new(10);
assert_eq!(ctrl.transition_ms(), 300);
ctrl.set_transition_ms(500);
assert_eq!(ctrl.transition_ms(), 500);
}
#[test]
fn projection_controller_show_indicator_toggle() {
let mut ctrl = PresentationController::new(5);
assert!(ctrl.show_indicator());
ctrl.set_show_indicator(false);
assert!(!ctrl.show_indicator());
}
}