iced_code_editor/canvas_editor/
view.rs

1//! Iced UI view and rendering logic.
2
3use iced::widget::canvas::Canvas;
4use iced::widget::{Row, Scrollable, Space, container, scrollable};
5use iced::{Background, Border, Color, Element, Length, Shadow};
6
7use super::{CodeEditor, GUTTER_WIDTH, LINE_HEIGHT, Message};
8
9impl CodeEditor {
10    /// Creates the view element with scrollable wrapper.
11    ///
12    /// The backgrounds (editor and gutter) are handled by container styles
13    /// to ensure proper clipping when the pane is resized.
14    pub fn view(&self) -> Element<'_, Message> {
15        // Calculate total content height based on actual lines
16        // Use max of content height and viewport height to ensure the canvas
17        // always covers the visible area (prevents visual artifacts when
18        // content is shorter than viewport after reset/file change)
19        let total_lines = self.buffer.line_count();
20        let content_height = total_lines as f32 * LINE_HEIGHT;
21        let canvas_height = content_height.max(self.viewport_height);
22
23        // Create canvas with height that covers at least the viewport
24        let canvas = Canvas::new(self)
25            .width(Length::Fill)
26            .height(Length::Fixed(canvas_height));
27
28        // Capture style colors for closures
29        let scrollbar_bg = self.style.scrollbar_background;
30        let scroller_color = self.style.scroller_color;
31        let background_color = self.style.background;
32        let gutter_background = self.style.gutter_background;
33
34        // Wrap in scrollable for automatic scrollbar display with custom style
35        // Use Length::Fill to respect parent container constraints and enable proper clipping
36        // Background is TRANSPARENT here because it's handled by the Stack layer below
37        let scrollable = Scrollable::new(canvas)
38            .id(self.scrollable_id.clone())
39            .width(Length::Fill)
40            .height(Length::Fill)
41            .on_scroll(Message::Scrolled)
42            .style(move |_theme, _status| scrollable::Style {
43                container: container::Style {
44                    background: Some(Background::Color(Color::TRANSPARENT)),
45                    ..container::Style::default()
46                },
47                vertical_rail: scrollable::Rail {
48                    background: Some(scrollbar_bg.into()),
49                    border: Border {
50                        radius: 4.0.into(),
51                        width: 0.0,
52                        color: Color::TRANSPARENT,
53                    },
54                    scroller: scrollable::Scroller {
55                        background: scroller_color.into(),
56                        border: Border {
57                            radius: 4.0.into(),
58                            width: 0.0,
59                            color: Color::TRANSPARENT,
60                        },
61                    },
62                },
63                horizontal_rail: scrollable::Rail {
64                    background: Some(scrollbar_bg.into()),
65                    border: Border {
66                        radius: 4.0.into(),
67                        width: 0.0,
68                        color: Color::TRANSPARENT,
69                    },
70                    scroller: scrollable::Scroller {
71                        background: scroller_color.into(),
72                        border: Border {
73                            radius: 4.0.into(),
74                            width: 0.0,
75                            color: Color::TRANSPARENT,
76                        },
77                    },
78                },
79                gap: None,
80                auto_scroll: scrollable::AutoScroll {
81                    background: Color::TRANSPARENT.into(),
82                    border: Border::default(),
83                    shadow: Shadow::default(),
84                    icon: Color::TRANSPARENT,
85                },
86            });
87
88        // Gutter background container (fixed width, clipped by parent)
89        let gutter_container =
90            container(Space::new().width(Length::Fill).height(Length::Fill))
91                .width(Length::Fixed(GUTTER_WIDTH))
92                .height(Length::Fill)
93                .style(move |_| container::Style {
94                    background: Some(Background::Color(gutter_background)),
95                    ..container::Style::default()
96                });
97
98        // Code background container (fills remaining width)
99        let code_background_container =
100            container(Space::new().width(Length::Fill).height(Length::Fill))
101                .width(Length::Fill)
102                .height(Length::Fill)
103                .style(move |_| container::Style {
104                    background: Some(Background::Color(background_color)),
105                    ..container::Style::default()
106                });
107
108        // Main layout: use a Stack to layer the backgrounds behind the scrollable
109        // The scrollable has a transparent background so the colors show through
110        let editor_content = iced::widget::Stack::new()
111            .push(
112                // Background layer (bottom): gutter + code backgrounds
113                Row::new()
114                    .push(gutter_container)
115                    .push(code_background_container),
116            )
117            .push(
118                // Scrollable layer (top) - transparent, overlays the backgrounds
119                scrollable,
120            );
121
122        // Wrap in a container with clip to ensure proper bounds
123        container(editor_content)
124            .width(Length::Fill)
125            .height(Length::Fill)
126            .clip(true)
127            .into()
128    }
129}