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 only
16        let total_lines = self.buffer.line_count();
17        let content_height = total_lines as f32 * LINE_HEIGHT;
18
19        // Create canvas with height based on content only
20        // The scrollable wrapper will handle the viewport constraints
21        let canvas = Canvas::new(self)
22            .width(Length::Fill)
23            .height(Length::Fixed(content_height));
24
25        // Capture style colors for closures
26        let scrollbar_bg = self.style.scrollbar_background;
27        let scroller_color = self.style.scroller_color;
28        let background_color = self.style.background;
29        let gutter_background = self.style.gutter_background;
30
31        // Wrap in scrollable for automatic scrollbar display with custom style
32        // Use Length::Fill to respect parent container constraints and enable proper clipping
33        // Background is TRANSPARENT here because it's handled by the Stack layer below
34        let scrollable = Scrollable::new(canvas)
35            .id(self.scrollable_id.clone())
36            .width(Length::Fill)
37            .height(Length::Fill)
38            .on_scroll(Message::Scrolled)
39            .style(move |_theme, _status| scrollable::Style {
40                container: container::Style {
41                    background: Some(Background::Color(Color::TRANSPARENT)),
42                    ..container::Style::default()
43                },
44                vertical_rail: scrollable::Rail {
45                    background: Some(scrollbar_bg.into()),
46                    border: Border {
47                        radius: 4.0.into(),
48                        width: 0.0,
49                        color: Color::TRANSPARENT,
50                    },
51                    scroller: scrollable::Scroller {
52                        background: scroller_color.into(),
53                        border: Border {
54                            radius: 4.0.into(),
55                            width: 0.0,
56                            color: Color::TRANSPARENT,
57                        },
58                    },
59                },
60                horizontal_rail: scrollable::Rail {
61                    background: Some(scrollbar_bg.into()),
62                    border: Border {
63                        radius: 4.0.into(),
64                        width: 0.0,
65                        color: Color::TRANSPARENT,
66                    },
67                    scroller: scrollable::Scroller {
68                        background: scroller_color.into(),
69                        border: Border {
70                            radius: 4.0.into(),
71                            width: 0.0,
72                            color: Color::TRANSPARENT,
73                        },
74                    },
75                },
76                gap: None,
77                auto_scroll: scrollable::AutoScroll {
78                    background: Color::TRANSPARENT.into(),
79                    border: Border::default(),
80                    shadow: Shadow::default(),
81                    icon: Color::TRANSPARENT,
82                },
83            });
84
85        // Gutter background container (fixed width, clipped by parent)
86        let gutter_container =
87            container(Space::new().width(Length::Fill).height(Length::Fill))
88                .width(Length::Fixed(GUTTER_WIDTH))
89                .height(Length::Fill)
90                .style(move |_| container::Style {
91                    background: Some(Background::Color(gutter_background)),
92                    ..container::Style::default()
93                });
94
95        // Code background container (fills remaining width)
96        let code_background_container =
97            container(Space::new().width(Length::Fill).height(Length::Fill))
98                .width(Length::Fill)
99                .height(Length::Fill)
100                .style(move |_| container::Style {
101                    background: Some(Background::Color(background_color)),
102                    ..container::Style::default()
103                });
104
105        // Main layout: use a Stack to layer the backgrounds behind the scrollable
106        // The scrollable has a transparent background so the colors show through
107        let editor_content = iced::widget::Stack::new()
108            .push(
109                // Background layer (bottom): gutter + code backgrounds
110                Row::new()
111                    .push(gutter_container)
112                    .push(code_background_container),
113            )
114            .push(
115                // Scrollable layer (top) - transparent, overlays the backgrounds
116                scrollable,
117            );
118
119        // Wrap in a container with clip to ensure proper bounds
120        container(editor_content)
121            .width(Length::Fill)
122            .height(Length::Fill)
123            .clip(true)
124            .into()
125    }
126}