Skip to main content

appcui/ui/canvas/
canvas.rs

1use crate::prelude::*;
2use crate::ui::canvas::initialization_flags::Flags;
3
4use self::components::ScrollBars;
5
6#[CustomControl(overwrite=OnPaint+OnKeyPressed+OnMouseEvent+OnResize, internal=true)]
7pub struct Canvas {
8    surface: Surface,
9    x: i32,
10    y: i32,
11    background: Option<Character>,
12    flags: Flags,
13    drag_point: Option<Point>,
14    scrollbars: ScrollBars
15}
16impl Canvas {
17    /// Creates a new canvas with the specified size, layout and flags.
18    /// The flags can be a combination of the following values:
19    /// * `Flags::ScrollBars` - if set, the canvas will have horizontal and vertical scrollbars
20    /// 
21    /// The parameter `canvas_size` is the size of the canvas (in characters), while the layout is the layout of the control.
22    /// 
23    /// # Example
24    /// ```rust, no_run
25    /// use appcui::prelude::*;
26    /// let mut canvas = Canvas::new(Size::new(100, 100), layout!("x:1,y:1,w:30,h:10"), canvas::Flags::ScrollBars); 
27    /// ```
28    pub fn new(canvas_size: Size, layout: Layout, flags: Flags) -> Self {
29        Self {
30            base: ControlBase::with_status_flags(
31                layout,
32                (StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput)
33                    | if flags == Flags::ScrollBars {
34                        StatusFlags::IncreaseBottomMarginOnFocus | StatusFlags::IncreaseRightMarginOnFocus
35                    } else {
36                        StatusFlags::None
37                    },
38            ),
39            surface: Surface::new(canvas_size.width, canvas_size.height),
40            x: 0,
41            y: 0,
42            flags,
43            background: None,
44            drag_point: None,
45            scrollbars: ScrollBars::new(flags == Flags::ScrollBars)
46        }
47    }
48
49
50    /// Resizes the inner surface of the canvas. This will also update the scrollbars if they are present.
51    /// # Example
52    /// ```rust, no_run
53    /// use appcui::prelude::*;
54    /// let mut canvas = Canvas::new(Size::new(100, 100), layout!("x:1,y:1,w:30,h:10"), canvas::Flags::ScrollBars);
55    /// canvas.resize_surface(Size::new(200, 200));
56    /// ```
57    pub fn resize_surface(&mut self, new_size: Size) {
58        self.surface.resize(new_size);
59        let sz = self.surface.size();
60        self.scrollbars.update(sz.width as u64, sz.height as u64, self.size());
61        self.move_scroll_to(self.x, self.y);
62    }
63
64    /// Returns a mutable reference to the inner surface of the canvas.
65    #[inline(always)]
66    pub fn drawing_surface_mut(&mut self) -> &mut Surface {
67        &mut self.surface
68    }
69
70    /// Sets the background of the canvas to the specified character.
71    /// # Example
72    /// ```rust, no_run
73    /// use appcui::prelude::*;
74    /// let mut canvas = Canvas::new(Size::new(100, 100), layout!("x:1,y:1,w:30,h:10"), canvas::Flags::ScrollBars);
75    /// canvas.set_background(Character::new('*', Color::White, Color::Black, CharFlags::None));
76    /// ```
77    pub fn set_background(&mut self, backgroud_char: Character) {
78        self.background = Some(backgroud_char);
79    }
80
81    /// Clears the background character of the canvas. It esentially resets it to transparent foreground and backgroud colors
82    pub fn clear_background(&mut self) {
83        self.background = None;
84    }
85
86    fn move_scroll_to(&mut self, x: i32, y: i32) {
87        let sz = self.size();
88        let surface_size = self.surface.size();
89        self.x = if surface_size.width <= sz.width {
90            0
91        } else {
92            x.max((sz.width as i32) - (surface_size.width as i32))
93        };
94        self.y = if surface_size.height <= sz.height {
95            0
96        } else {
97            y.max((sz.height as i32) - (surface_size.height as i32))
98        };
99        self.x = self.x.min(0);
100        self.y = self.y.min(0);
101        self.scrollbars.set_indexes((-self.x) as u64, (-self.y) as u64);
102    }
103    fn update_scroll_pos_from_scrollbars(&mut self) {
104        let h = -(self.scrollbars.horizontal_index() as i32);
105        let v = -(self.scrollbars.vertical_index() as i32);
106        self.move_scroll_to(h,v);
107    }
108}
109impl OnResize for Canvas {
110    fn on_resize(&mut self, _old_size: Size, _new_size: Size) {
111        let paint_sz = self.surface.size();
112        self.scrollbars.resize(paint_sz.width as u64, paint_sz.height as u64,&self.base);
113        self.move_scroll_to(self.x, self.y);
114    }
115}
116impl OnPaint for Canvas {
117    fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
118        if (self.has_focus()) && (self.flags == Flags::ScrollBars) {
119            self.scrollbars.paint(surface, theme, self);
120            surface.reduce_clip_by(0,0,1,1);
121        }
122        if let Some(back) = self.background {
123            surface.clear(back);
124        }
125        surface.draw_surface(self.x, self.y, &self.surface);
126    }
127}
128impl OnKeyPressed for Canvas {
129    fn on_key_pressed(&mut self, key: Key, _character: char) -> EventProcessStatus {
130        match key.value() {
131            key!("Left") => {
132                self.move_scroll_to(self.x + 1, self.y);
133                EventProcessStatus::Processed
134            }
135            key!("Right") => {
136                self.move_scroll_to(self.x - 1, self.y);
137                EventProcessStatus::Processed
138            }
139            key!("Up") => {
140                self.move_scroll_to(self.x, self.y + 1);
141                EventProcessStatus::Processed
142            }
143            key!("Down") => {
144                self.move_scroll_to(self.x, self.y - 1);
145                EventProcessStatus::Processed
146            }
147            key!("Shift+Left") => {
148                self.move_scroll_to(0, self.y);
149                EventProcessStatus::Processed
150            }
151            key!("Shift+Right") => {
152                self.move_scroll_to(i32::MIN, self.y);
153                EventProcessStatus::Processed
154            }
155            key!("Shift+Up") => {
156                self.move_scroll_to(self.x, 0);
157                EventProcessStatus::Processed
158            }
159            key!("Shift+Down") => {
160                self.move_scroll_to(self.x, i32::MIN);
161                EventProcessStatus::Processed
162            }
163            key!("Ctrl+Left") => {
164                self.move_scroll_to(self.x + self.size().width as i32, self.y);
165                EventProcessStatus::Processed
166            }
167            key!("Ctrl+Right") => {
168                self.move_scroll_to(self.x - self.size().width as i32, self.y);
169                EventProcessStatus::Processed
170            }
171            key!("Ctrl+Up") | key!("PageUp")=> {
172                self.move_scroll_to(self.x, self.y + self.size().height as i32);
173                EventProcessStatus::Processed
174            }
175            key!("Ctrl+Down") | key!("PageDown")=> {
176                self.move_scroll_to(self.x, self.y - self.size().height as i32);
177                EventProcessStatus::Processed
178            }
179            key!("Home") => {
180                self.move_scroll_to(0, 0);
181                EventProcessStatus::Processed
182            }
183            key!("End") => {
184                self.move_scroll_to(i32::MIN, i32::MIN);
185                EventProcessStatus::Processed
186            }
187            _ => EventProcessStatus::Ignored,
188        }
189    }
190}
191impl OnMouseEvent for Canvas {
192    fn on_mouse_event(&mut self, event: &MouseEvent) -> EventProcessStatus {
193        if self.scrollbars.process_mouse_event(event) {
194            self.update_scroll_pos_from_scrollbars();
195            return EventProcessStatus::Processed;
196        }
197        let response = match event {
198            MouseEvent::Enter => EventProcessStatus::Ignored,
199            MouseEvent::Leave => EventProcessStatus::Ignored,
200            MouseEvent::Over(_) => EventProcessStatus::Ignored,
201            MouseEvent::Pressed(data) => {
202                if (self.flags == Flags::ScrollBars) && (self.has_focus()) {
203                    let sz = self.size();
204                    if (data.x == sz.width as i32) || (data.y == sz.height as i32) {
205                        return EventProcessStatus::Ignored;
206                    }
207                }
208                self.drag_point = Some(Point::new(data.x, data.y));
209                EventProcessStatus::Processed
210            }
211            MouseEvent::Released(data) => {
212                if let Some(p) = self.drag_point {
213                    self.move_scroll_to(self.x + data.x - p.x, self.y + data.y - p.y);
214                }
215                self.drag_point = None;
216                EventProcessStatus::Processed
217            }
218            MouseEvent::DoubleClick(_) => EventProcessStatus::Ignored,
219            MouseEvent::Drag(data) => {
220                if let Some(p) = self.drag_point {
221                    self.move_scroll_to(self.x + data.x - p.x, self.y + data.y - p.y);
222                }
223                self.drag_point = Some(Point::new(data.x, data.y));
224                EventProcessStatus::Processed
225            }
226            MouseEvent::Wheel(dir) => {
227                match dir {
228                    MouseWheelDirection::Left => self.move_scroll_to(self.x + 1, self.y),
229                    MouseWheelDirection::Right => self.move_scroll_to(self.x - 1, self.y),
230                    MouseWheelDirection::Up => self.move_scroll_to(self.x, self.y + 1),
231                    MouseWheelDirection::Down => self.move_scroll_to(self.x, self.y - 1),
232                };
233                EventProcessStatus::Processed
234            }
235        };
236        // if one of the components require a repaint, than we should repaint even if the canvas required us to ignore the event
237        if self.scrollbars.should_repaint() {
238            EventProcessStatus::Processed
239        } else {
240            response
241        }
242    }
243}
244