1use std::sync::Arc;
2
3use crate::theme::Theme;
4
5#[cfg(feature = "syntax")]
6use crate::syntax_highlighting::SyntaxHighlighter;
7
8#[doc = include_str!("../docs/view_context.md")]
9#[derive(Clone)]
10pub struct ViewContext {
11 pub size: Size,
15 pub theme: Arc<Theme>,
16 #[cfg(feature = "syntax")]
17 pub(crate) highlighter: Arc<SyntaxHighlighter>,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Size {
28 pub width: u16,
29 pub height: u16,
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub struct Insets {
38 pub left: u16,
39 pub right: u16,
40 pub top: u16,
41 pub bottom: u16,
42}
43
44impl Insets {
45 pub fn new(left: u16, right: u16, top: u16, bottom: u16) -> Self {
46 Self { left, right, top, bottom }
47 }
48
49 pub fn all(amount: u16) -> Self {
51 Self { left: amount, right: amount, top: amount, bottom: amount }
52 }
53
54 pub fn symmetric(horizontal: u16, vertical: u16) -> Self {
56 Self { left: horizontal, right: horizontal, top: vertical, bottom: vertical }
57 }
58
59 pub fn horizontal(amount: u16) -> Self {
61 Self::symmetric(amount, 0)
62 }
63
64 pub fn vertical(amount: u16) -> Self {
66 Self::symmetric(0, amount)
67 }
68}
69
70impl ViewContext {
71 pub fn new(size: impl Into<Size>) -> Self {
72 Self::new_with_theme(size, Theme::default())
73 }
74
75 pub fn new_with_theme(size: impl Into<Size>, theme: Theme) -> Self {
76 Self {
77 size: size.into(),
78 theme: Arc::new(theme),
79 #[cfg(feature = "syntax")]
80 highlighter: Arc::new(SyntaxHighlighter::new()),
81 }
82 }
83
84 #[cfg(feature = "syntax")]
85 pub fn highlighter(&self) -> &SyntaxHighlighter {
86 &self.highlighter
87 }
88
89 pub fn with_size(&self, size: impl Into<Size>) -> Self {
91 Self {
92 size: size.into(),
93 theme: self.theme.clone(),
94 #[cfg(feature = "syntax")]
95 highlighter: self.highlighter.clone(),
96 }
97 }
98
99 pub fn with_width(&self, width: u16) -> Self {
101 self.with_size((width, self.size.height))
102 }
103
104 pub fn with_height(&self, height: u16) -> Self {
106 self.with_size((self.size.width, height))
107 }
108
109 pub fn inset(&self, insets: Insets) -> Self {
112 let width = self.size.width.saturating_sub(insets.left).saturating_sub(insets.right);
113 let height = self.size.height.saturating_sub(insets.top).saturating_sub(insets.bottom);
114 self.with_size((width, height))
115 }
116}
117
118impl From<(u16, u16)> for Size {
119 fn from((width, height): (u16, u16)) -> Self {
120 Self { width, height }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::theme::Theme;
128
129 fn ctx(width: u16, height: u16) -> ViewContext {
130 ViewContext::new_with_theme((width, height), Theme::default())
131 }
132
133 #[test]
134 fn with_width_replaces_width_and_keeps_height() {
135 let parent = ctx(80, 24);
136 let child = parent.with_width(40);
137 assert_eq!(child.size.width, 40);
138 assert_eq!(child.size.height, 24);
139 }
140
141 #[test]
142 fn with_width_preserves_theme_arc() {
143 let parent = ctx(80, 24);
144 let child = parent.with_width(40);
145 assert!(Arc::ptr_eq(&parent.theme, &child.theme));
146 }
147
148 #[test]
149 fn with_height_replaces_height_and_keeps_width() {
150 let parent = ctx(80, 24);
151 let child = parent.with_height(10);
152 assert_eq!(child.size.width, 80);
153 assert_eq!(child.size.height, 10);
154 }
155
156 #[test]
157 fn inset_subtracts_on_all_sides() {
158 let parent = ctx(80, 24);
159 let child = parent.inset(Insets::new(2, 3, 1, 4));
160 assert_eq!(child.size.width, 80 - 2 - 3);
161 assert_eq!(child.size.height, 24 - 1 - 4);
162 }
163
164 #[test]
165 fn inset_saturates_at_zero() {
166 let parent = ctx(4, 4);
167 let child = parent.inset(Insets::all(10));
168 assert_eq!(child.size.width, 0);
169 assert_eq!(child.size.height, 0);
170 }
171
172 #[test]
173 fn insets_symmetric_sets_opposite_sides_equal() {
174 let insets = Insets::symmetric(3, 5);
175 assert_eq!(insets.left, 3);
176 assert_eq!(insets.right, 3);
177 assert_eq!(insets.top, 5);
178 assert_eq!(insets.bottom, 5);
179 }
180
181 #[test]
182 fn insets_horizontal_only_affects_left_and_right() {
183 let insets = Insets::horizontal(4);
184 assert_eq!(insets.left, 4);
185 assert_eq!(insets.right, 4);
186 assert_eq!(insets.top, 0);
187 assert_eq!(insets.bottom, 0);
188 }
189
190 #[test]
191 fn insets_vertical_only_affects_top_and_bottom() {
192 let insets = Insets::vertical(2);
193 assert_eq!(insets.left, 0);
194 assert_eq!(insets.right, 0);
195 assert_eq!(insets.top, 2);
196 assert_eq!(insets.bottom, 2);
197 }
198
199 #[test]
200 fn inset_horizontal_only_shrinks_width() {
201 let parent = ctx(80, 24);
202 let child = parent.inset(Insets::horizontal(2));
203 assert_eq!(child.size.width, 76);
204 assert_eq!(child.size.height, 24);
205 }
206}