bubbletea_widgets/
paginator.rs

1//! A paginator component for bubbletea-rs, ported from the Go version.
2//!
3//! This component is used for calculating pagination and rendering pagination info.
4//! Note that this package does not render actual pages of content; it's purely
5//! for handling the state and view of the pagination control itself.
6
7use crate::key::{self, KeyMap as KeyMapTrait};
8use bubbletea_rs::{KeyMsg, Msg};
9
10/// The type of pagination to display.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum Type {
13    /// Display pagination as Arabic numerals (e.g., "1/5").
14    #[default]
15    Arabic,
16    /// Display pagination as dots (e.g., "● ○ ○ ○ ○").
17    Dots,
18}
19
20/// Key bindings for different actions within the paginator.
21///
22/// This structure defines the key bindings that control pagination navigation.
23/// It implements the `KeyMap` trait to provide help information for the
24/// paginator component.
25///
26/// # Examples
27///
28/// ```rust
29/// use bubbletea_widgets::paginator::PaginatorKeyMap;
30/// use bubbletea_widgets::key;
31///
32/// let keymap = PaginatorKeyMap::default();
33///
34/// // Create custom key bindings
35/// let custom_keymap = PaginatorKeyMap {
36///     prev_page: key::new_binding(vec![
37///         key::with_keys_str(&["a", "left"]),
38///         key::with_help("a/←", "previous page"),
39///     ]),
40///     next_page: key::new_binding(vec![
41///         key::with_keys_str(&["d", "right"]),
42///         key::with_help("d/→", "next page"),
43///     ]),
44/// };
45/// ```
46#[derive(Debug, Clone)]
47pub struct PaginatorKeyMap {
48    /// Key binding for navigating to the previous page.
49    /// Default keys: PageUp, Left Arrow, 'h'
50    pub prev_page: key::Binding,
51    /// Key binding for navigating to the next page.
52    /// Default keys: PageDown, Right Arrow, 'l'
53    pub next_page: key::Binding,
54}
55
56impl Default for PaginatorKeyMap {
57    /// Creates default key bindings for paginator navigation.
58    ///
59    /// The default key bindings are:
60    /// - **Previous page**: PageUp, Left Arrow, 'h'
61    /// - **Next page**: PageDown, Right Arrow, 'l'
62    ///
63    /// These bindings are commonly used in terminal applications and provide
64    /// both arrow key navigation and vim-style 'h'/'l' keys.
65    ///
66    /// # Examples
67    ///
68    /// ```rust
69    /// use bubbletea_widgets::paginator::PaginatorKeyMap;
70    /// use bubbletea_widgets::key::KeyMap;
71    ///
72    /// let keymap = PaginatorKeyMap::default();
73    /// let help = keymap.short_help();
74    /// assert_eq!(help.len(), 2); // prev and next bindings
75    /// ```
76    fn default() -> Self {
77        Self {
78            prev_page: key::new_binding(vec![
79                key::with_keys_str(&["pgup", "left", "h"]),
80                key::with_help("←/h", "prev page"),
81            ]),
82            next_page: key::new_binding(vec![
83                key::with_keys_str(&["pgdown", "right", "l"]),
84                key::with_help("→/l", "next page"),
85            ]),
86        }
87    }
88}
89
90impl KeyMapTrait for PaginatorKeyMap {
91    /// Returns key bindings for the short help view.
92    ///
93    /// This provides the essential pagination key bindings that will be
94    /// displayed in compact help views.
95    ///
96    /// # Returns
97    ///
98    /// A vector containing references to the previous page and next page bindings.
99    fn short_help(&self) -> Vec<&key::Binding> {
100        vec![&self.prev_page, &self.next_page]
101    }
102
103    /// Returns key bindings for the full help view.
104    ///
105    /// This organizes all pagination key bindings into columns for display
106    /// in expanded help views. Since pagination only has two keys, they're
107    /// grouped together in a single column.
108    ///
109    /// # Returns
110    ///
111    /// A vector of vectors, where each inner vector represents a column
112    /// of related key bindings.
113    fn full_help(&self) -> Vec<Vec<&key::Binding>> {
114        vec![vec![&self.prev_page, &self.next_page]]
115    }
116}
117
118/// A paginator model for handling pagination state and rendering.
119///
120/// This component manages pagination state including current page, total pages,
121/// and pagination display style. It can render pagination in two modes:
122/// - **Arabic**: Shows page numbers (e.g., "3/10")
123/// - **Dots**: Shows dots representing pages (e.g., "○ ○ ● ○ ○")
124///
125/// The paginator handles key bindings for navigation and provides helper methods
126/// for calculating slice bounds and page information.
127///
128/// # Examples
129///
130/// ## Basic Usage
131///
132/// ```rust
133/// use bubbletea_widgets::paginator::{Model, Type};
134///
135/// let mut paginator = Model::new()
136///     .with_per_page(10)
137///     .with_total_items(150); // Creates 15 pages
138///
139/// assert_eq!(paginator.total_pages, 15);
140/// assert!(paginator.on_first_page());
141///
142/// paginator.next_page();
143/// assert_eq!(paginator.page, 1);
144/// ```
145///
146/// ## Different Display Types
147///
148/// ```rust
149/// use bubbletea_widgets::paginator::{Model, Type};
150///
151/// let mut paginator = Model::new()
152///     .with_total_items(50)
153///     .with_per_page(10);
154///
155/// // Arabic mode (default): "1/5"
156/// paginator.paginator_type = Type::Arabic;
157/// let arabic_view = paginator.view();
158///
159/// // Dots mode: "● ○ ○ ○ ○"
160/// paginator.paginator_type = Type::Dots;
161/// let dots_view = paginator.view();
162/// ```
163///
164/// ## Integration with bubbletea-rs
165///
166/// ```rust
167/// use bubbletea_widgets::paginator::Model as Paginator;
168/// use bubbletea_rs::{Model, Cmd, Msg};
169///
170/// struct App {
171///     paginator: Paginator,
172///     items: Vec<String>,
173/// }
174///
175/// impl Model for App {
176///     fn init() -> (Self, Option<Cmd>) {
177///         let items: Vec<String> = (1..=100).map(|i| format!("Item {}", i)).collect();
178///         let paginator = Paginator::new()
179///             .with_per_page(10)
180///             .with_total_items(items.len());
181///             
182///         (Self { paginator, items }, None)
183///     }
184///
185///     fn update(&mut self, msg: Msg) -> Option<Cmd> {
186///         self.paginator.update(&msg);
187///         None
188///     }
189///
190///     fn view(&self) -> String {
191///         let (start, end) = self.paginator.get_slice_bounds(self.items.len());
192///         let page_items: Vec<String> = self.items[start..end].to_vec();
193///         
194///         format!(
195///             "Items:\n{}\n\nPage: {}",
196///             page_items.join("\n"),
197///             self.paginator.view()
198///         )
199///     }
200/// }
201/// ```
202#[derive(Debug, Clone)]
203pub struct Model {
204    /// The type of pagination to display (Dots or Arabic).
205    pub paginator_type: Type,
206    /// The current page.
207    pub page: usize,
208    /// The number of items per page.
209    pub per_page: usize,
210    /// The total number of pages.
211    pub total_pages: usize,
212
213    /// The character to use for the active page in Dots mode.
214    pub active_dot: String,
215    /// The character to use for inactive pages in Dots mode.
216    pub inactive_dot: String,
217    /// The format string for Arabic mode (e.g., "%d/%d").
218    pub arabic_format: String,
219
220    /// Key bindings.
221    pub keymap: PaginatorKeyMap,
222}
223
224impl Default for Model {
225    /// Creates a paginator with default settings.
226    ///
227    /// Default configuration:
228    /// - Type: Arabic ("1/5" style)
229    /// - Current page: 0 (first page)
230    /// - Items per page: 1
231    /// - Total pages: 1
232    /// - Active dot: "•" (for dots mode)
233    /// - Inactive dot: "○" (for dots mode)
234    /// - Arabic format: "%d/%d" (current/total)
235    /// - Default key bindings
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// use bubbletea_widgets::paginator::{Model, Type};
241    ///
242    /// let paginator = Model::default();
243    /// assert_eq!(paginator.paginator_type, Type::Arabic);
244    /// assert_eq!(paginator.page, 0);
245    /// assert_eq!(paginator.per_page, 1);
246    /// assert_eq!(paginator.total_pages, 1);
247    /// ```
248    fn default() -> Self {
249        Self {
250            paginator_type: Type::default(),
251            page: 0,
252            per_page: 1,
253            total_pages: 1,
254            active_dot: "•".to_string(),
255            inactive_dot: "○".to_string(),
256            arabic_format: "%d/%d".to_string(),
257            keymap: PaginatorKeyMap::default(),
258        }
259    }
260}
261
262impl Model {
263    /// Creates a new paginator model with default settings.
264    ///
265    /// This is equivalent to calling `Model::default()` but provides a more
266    /// conventional constructor-style API.
267    ///
268    /// # Examples
269    ///
270    /// ```rust
271    /// use bubbletea_widgets::paginator::Model;
272    ///
273    /// let paginator = Model::new();
274    /// assert_eq!(paginator.page, 0);
275    /// assert_eq!(paginator.total_pages, 1);
276    /// ```
277    pub fn new() -> Self {
278        Self::default()
279    }
280
281    /// Sets the total number of items and calculates total pages (builder pattern).
282    ///
283    /// This method automatically calculates the total number of pages based on
284    /// the total items and the current `per_page` setting. If the current page
285    /// becomes out of bounds, it will be adjusted to the last valid page.
286    ///
287    /// # Arguments
288    ///
289    /// * `items` - The total number of items to paginate
290    ///
291    /// # Examples
292    ///
293    /// ```rust
294    /// use bubbletea_widgets::paginator::Model;
295    ///
296    /// let paginator = Model::new()
297    ///     .with_per_page(10)
298    ///     .with_total_items(95); // Will create 10 pages (95/10 = 9.5 -> 10)
299    ///
300    /// assert_eq!(paginator.total_pages, 10);
301    /// ```
302    pub fn with_total_items(mut self, items: usize) -> Self {
303        self.set_total_items(items);
304        self
305    }
306
307    /// Sets the number of items per page (builder pattern).
308    ///
309    /// The minimum value is 1; any value less than 1 will be clamped to 1.
310    /// This setting affects how total pages are calculated when using
311    /// `set_total_items()` or `with_total_items()`.
312    ///
313    /// # Arguments
314    ///
315    /// * `per_page` - Number of items to display per page (minimum 1)
316    ///
317    /// # Examples
318    ///
319    /// ```rust
320    /// use bubbletea_widgets::paginator::Model;
321    ///
322    /// let paginator = Model::new()
323    ///     .with_per_page(25)
324    ///     .with_total_items(100); // Will create 4 pages
325    ///
326    /// assert_eq!(paginator.per_page, 25);
327    /// assert_eq!(paginator.total_pages, 4);
328    ///
329    /// // Values less than 1 are clamped to 1
330    /// let clamped = Model::new().with_per_page(0);
331    /// assert_eq!(clamped.per_page, 1);
332    /// ```
333    pub fn with_per_page(mut self, per_page: usize) -> Self {
334        self.per_page = per_page.max(1);
335        self
336    }
337
338    /// Sets the number of items per page (mutable version).
339    ///
340    /// The minimum value is 1; any value less than 1 will be clamped to 1.
341    /// This method modifies the paginator in place.
342    ///
343    /// # Arguments
344    ///
345    /// * `per_page` - Number of items to display per page (minimum 1)
346    ///
347    /// # Examples
348    ///
349    /// ```rust
350    /// use bubbletea_widgets::paginator::Model;
351    ///
352    /// let mut paginator = Model::new();
353    /// paginator.set_per_page(15);
354    /// assert_eq!(paginator.per_page, 15);
355    ///
356    /// // Values less than 1 are clamped to 1
357    /// paginator.set_per_page(0);
358    /// assert_eq!(paginator.per_page, 1);
359    /// ```
360    pub fn set_per_page(&mut self, per_page: usize) {
361        self.per_page = per_page.max(1);
362    }
363
364    /// Sets the total number of pages directly.
365    ///
366    /// The minimum value is 1; any value less than 1 will be clamped to 1.
367    /// If the current page becomes out of bounds after setting the total pages,
368    /// it will be adjusted to the last valid page.
369    ///
370    /// **Note**: This method sets pages directly. If you want to calculate pages
371    /// based on total items, use `set_total_items()` instead.
372    ///
373    /// # Arguments
374    ///
375    /// * `pages` - The total number of pages (minimum 1)
376    ///
377    /// # Examples
378    ///
379    /// ```rust
380    /// use bubbletea_widgets::paginator::Model;
381    ///
382    /// let mut paginator = Model::new();
383    /// paginator.set_total_pages(10);
384    /// assert_eq!(paginator.total_pages, 10);
385    ///
386    /// // If current page is out of bounds, it gets adjusted
387    /// paginator.page = 15; // Out of bounds
388    /// paginator.set_total_pages(5);
389    /// assert_eq!(paginator.page, 4); // Adjusted to last page (0-indexed)
390    /// ```
391    pub fn set_total_pages(&mut self, pages: usize) {
392        self.total_pages = pages.max(1);
393        // Ensure the current page is not out of bounds
394        if self.page >= self.total_pages {
395            self.page = self.total_pages.saturating_sub(1);
396        }
397    }
398
399    /// Calculates and sets the total number of pages based on the total items.
400    ///
401    /// This method divides the total number of items by the current `per_page`
402    /// setting to calculate the total pages. The result is always at least 1,
403    /// even for 0 items. If the current page becomes out of bounds after
404    /// recalculation, it will be adjusted to the last valid page.
405    ///
406    /// # Arguments
407    ///
408    /// * `items` - The total number of items to paginate
409    ///
410    /// # Examples
411    ///
412    /// ```rust
413    /// use bubbletea_widgets::paginator::Model;
414    ///
415    /// let mut paginator = Model::new().with_per_page(10);
416    ///
417    /// // 95 items with 10 per page = 10 pages (95/10 = 9.5 -> 10)
418    /// paginator.set_total_items(95);
419    /// assert_eq!(paginator.total_pages, 10);
420    ///
421    /// // 0 items still results in 1 page minimum
422    /// paginator.set_total_items(0);
423    /// assert_eq!(paginator.total_pages, 1);
424    ///
425    /// // Exact division
426    /// paginator.set_total_items(100);
427    /// assert_eq!(paginator.total_pages, 10);
428    /// ```
429    pub fn set_total_items(&mut self, items: usize) {
430        if items == 0 {
431            self.total_pages = 1;
432        } else {
433            self.total_pages = items.div_ceil(self.per_page);
434        }
435
436        // Ensure the current page is not out of bounds
437        if self.page >= self.total_pages {
438            self.page = self.total_pages.saturating_sub(1);
439        }
440    }
441
442    /// Returns the number of items on the current page.
443    ///
444    /// This method calculates how many items are actually present on the
445    /// current page, which may be less than `per_page` on the last page
446    /// or when there are fewer total items than `per_page`.
447    ///
448    /// # Arguments
449    ///
450    /// * `total_items` - The total number of items being paginated
451    ///
452    /// # Returns
453    ///
454    /// The number of items on the current page, or 0 if there are no items.
455    ///
456    /// # Examples
457    ///
458    /// ```rust
459    /// use bubbletea_widgets::paginator::Model;
460    ///
461    /// let mut paginator = Model::new().with_per_page(10);
462    ///
463    /// // Full page
464    /// assert_eq!(paginator.items_on_page(100), 10);
465    ///
466    /// // Partial last page
467    /// paginator.page = 9; // Last page (0-indexed)
468    /// assert_eq!(paginator.items_on_page(95), 5); // Only 5 items on page 10
469    ///
470    /// // No items
471    /// assert_eq!(paginator.items_on_page(0), 0);
472    /// ```
473    pub fn items_on_page(&self, total_items: usize) -> usize {
474        if total_items == 0 {
475            return 0;
476        }
477        let (start, end) = self.get_slice_bounds(total_items);
478        end - start
479    }
480
481    /// Calculates slice bounds for the current page.
482    ///
483    /// This is a helper function for paginating slices. Given the total length
484    /// of your data, it returns the start and end indices for the current page.
485    /// The returned bounds can be used directly with slice notation.
486    ///
487    /// # Arguments
488    ///
489    /// * `length` - The total length of the data being paginated
490    ///
491    /// # Returns
492    ///
493    /// A tuple `(start, end)` where:
494    /// - `start` is the inclusive start index for the current page
495    /// - `end` is the exclusive end index for the current page
496    ///
497    /// # Examples
498    ///
499    /// ```rust
500    /// use bubbletea_widgets::paginator::Model;
501    ///
502    /// let items: Vec<i32> = (1..=100).collect();
503    /// let mut paginator = Model::new().with_per_page(10);
504    ///
505    /// // First page (0)
506    /// let (start, end) = paginator.get_slice_bounds(items.len());
507    /// assert_eq!((start, end), (0, 10));
508    /// let page_items = &items[start..end]; // Items 1-10
509    ///
510    /// // Third page (2)
511    /// paginator.page = 2;
512    /// let (start, end) = paginator.get_slice_bounds(items.len());
513    /// assert_eq!((start, end), (20, 30));
514    /// let page_items = &items[start..end]; // Items 21-30
515    /// ```
516    pub fn get_slice_bounds(&self, length: usize) -> (usize, usize) {
517        let start = self.page * self.per_page;
518        let end = (start + self.per_page).min(length);
519        (start, end)
520    }
521
522    /// Returns slice bounds assuming maximum possible data length.
523    ///
524    /// This is a convenience method that calls `get_slice_bounds()` with
525    /// the maximum possible data length (`per_page * total_pages`). It's
526    /// useful when you know your data exactly fills the pagination structure.
527    ///
528    /// # Returns
529    ///
530    /// A tuple `(start, end)` representing slice bounds for the current page.
531    ///
532    /// # Examples
533    ///
534    /// ```rust
535    /// use bubbletea_widgets::paginator::Model;
536    ///
537    /// let mut paginator = Model::new()
538    ///     .with_per_page(10)
539    ///     .with_total_items(100); // Exactly 10 pages
540    ///
541    /// paginator.page = 3;
542    /// let (start, end) = paginator.start_index_end_index();
543    /// assert_eq!((start, end), (30, 40));
544    /// ```
545    pub fn start_index_end_index(&self) -> (usize, usize) {
546        self.get_slice_bounds(self.per_page * self.total_pages)
547    }
548
549    /// Navigates to the previous page.
550    ///
551    /// If the paginator is already on the first page (page 0), this method
552    /// has no effect. The page number will not go below 0.
553    ///
554    /// # Examples
555    ///
556    /// ```rust
557    /// use bubbletea_widgets::paginator::Model;
558    ///
559    /// let mut paginator = Model::new().with_per_page(10).with_total_items(100);
560    /// paginator.page = 5;
561    ///
562    /// paginator.prev_page();
563    /// assert_eq!(paginator.page, 4);
564    ///
565    /// // Won't go below 0
566    /// paginator.page = 0;
567    /// paginator.prev_page();
568    /// assert_eq!(paginator.page, 0);
569    /// ```
570    pub fn prev_page(&mut self) {
571        if self.page > 0 {
572            self.page -= 1;
573        }
574    }
575
576    /// Navigates to the next page.
577    ///
578    /// If the paginator is already on the last page, this method has no effect.
579    /// The page number will not exceed `total_pages - 1`.
580    ///
581    /// # Examples
582    ///
583    /// ```rust
584    /// use bubbletea_widgets::paginator::Model;
585    ///
586    /// let mut paginator = Model::new().with_per_page(10).with_total_items(100);
587    /// // total_pages = 10, so last page is 9 (0-indexed)
588    ///
589    /// paginator.page = 5;
590    /// paginator.next_page();
591    /// assert_eq!(paginator.page, 6);
592    ///
593    /// // Won't go beyond last page  
594    /// paginator.page = 8; // Second to last page
595    /// paginator.next_page();
596    /// assert_eq!(paginator.page, 9); // Should go to last page (9 is the last valid page)
597    /// paginator.next_page();
598    /// assert_eq!(paginator.page, 9); // Should stay at last page
599    /// ```
600    pub fn next_page(&mut self) {
601        if !self.on_last_page() {
602            self.page += 1;
603        }
604    }
605
606    /// Returns true if the paginator is on the first page.
607    ///
608    /// The first page is always page 0 in the 0-indexed pagination system.
609    ///
610    /// # Examples
611    ///
612    /// ```rust
613    /// use bubbletea_widgets::paginator::Model;
614    ///
615    /// let mut paginator = Model::new().with_per_page(10).with_total_items(100);
616    ///
617    /// assert!(paginator.on_first_page());
618    ///
619    /// paginator.next_page();
620    /// assert!(!paginator.on_first_page());
621    /// ```
622    pub fn on_first_page(&self) -> bool {
623        self.page == 0
624    }
625
626    /// Returns true if the paginator is on the last page.
627    ///
628    /// The last page is `total_pages - 1` in the 0-indexed pagination system.
629    ///
630    /// # Examples
631    ///
632    /// ```rust
633    /// use bubbletea_widgets::paginator::Model;
634    ///
635    /// let mut paginator = Model::new().with_per_page(10).with_total_items(90);
636    /// // Creates 9 pages (0-8), so last page is 8
637    ///
638    /// assert!(!paginator.on_last_page());
639    ///
640    /// paginator.page = 8; // Last page  
641    /// assert!(paginator.on_last_page());
642    /// ```
643    pub fn on_last_page(&self) -> bool {
644        self.page == self.total_pages.saturating_sub(1)
645    }
646
647    /// Updates the paginator based on received messages.
648    ///
649    /// This method should be called from your application's `update()` method
650    /// to handle pagination key presses. It automatically responds to the
651    /// configured key bindings for next/previous page navigation.
652    ///
653    /// # Arguments
654    ///
655    /// * `msg` - The message to process, typically containing key press events
656    ///
657    /// # Examples
658    ///
659    /// ```rust
660    /// use bubbletea_widgets::paginator::Model as Paginator;
661    /// use bubbletea_rs::{Model, Msg};
662    ///
663    /// struct App {
664    ///     paginator: Paginator,
665    /// }
666    ///
667    /// impl Model for App {
668    ///     fn update(&mut self, msg: Msg) -> Option<bubbletea_rs::Cmd> {
669    ///         // Forward messages to paginator
670    ///         self.paginator.update(&msg);
671    ///         None
672    ///     }
673    ///     
674    ///     // ... other methods
675    /// #   fn init() -> (Self, Option<bubbletea_rs::Cmd>) { (Self { paginator: Paginator::new() }, None) }
676    /// #   fn view(&self) -> String { String::new() }
677    /// }
678    /// ```
679    pub fn update(&mut self, msg: &Msg) {
680        if let Some(key_msg) = msg.downcast_ref::<KeyMsg>() {
681            if self.keymap.next_page.matches(key_msg) {
682                self.next_page();
683            } else if self.keymap.prev_page.matches(key_msg) {
684                self.prev_page();
685            }
686        }
687    }
688
689    /// Renders the paginator as a string.
690    ///
691    /// The output format depends on the `paginator_type` setting:
692    /// - **Arabic**: Shows "current/total" (e.g., "3/10")
693    /// - **Dots**: Shows dots with active page highlighted (e.g., "○ ○ ● ○ ○")
694    ///
695    /// # Returns
696    ///
697    /// A string representation of the current pagination state.
698    ///
699    /// # Examples
700    ///
701    /// ```rust
702    /// use bubbletea_widgets::paginator::{Model, Type};
703    ///
704    /// let mut paginator = Model::new().with_per_page(10).with_total_items(50);
705    /// // Creates 5 pages, currently on page 0
706    ///
707    /// // Arabic mode (default)
708    /// paginator.paginator_type = Type::Arabic;
709    /// assert_eq!(paginator.view(), "1/5"); // 1-indexed for display
710    ///
711    /// // Dots mode  
712    /// paginator.paginator_type = Type::Dots;
713    /// assert_eq!(paginator.view(), "• ○ ○ ○ ○");
714    ///
715    /// // Move to page 2
716    /// paginator.page = 2;
717    /// assert_eq!(paginator.view(), "○ ○ • ○ ○");
718    /// ```
719    pub fn view(&self) -> String {
720        match self.paginator_type {
721            Type::Arabic => self.arabic_view(),
722            Type::Dots => self.dots_view(),
723        }
724    }
725
726    fn arabic_view(&self) -> String {
727        self.arabic_format
728            .replacen("%d", &(self.page + 1).to_string(), 1)
729            .replacen("%d", &self.total_pages.to_string(), 1)
730    }
731
732    fn dots_view(&self) -> String {
733        let mut s = String::new();
734        for i in 0..self.total_pages {
735            if i == self.page {
736                s.push_str(&self.active_dot);
737            } else {
738                s.push_str(&self.inactive_dot);
739            }
740            if i < self.total_pages - 1 {
741                s.push(' ');
742            }
743        }
744        s
745    }
746}